Skip to content

[Feature] Add CMAKE vcpkg #297

@nmetulev

Description

@nmetulev

Spec: CMake / vcpkg Integration for winapp CLI

C++ developers using winapp CLI with CMake currently face significant friction. The existing approach requires 60-80 lines of boilerplate CMake code to: download the CLI, restore SDK headers, resolve package paths, and apply debug identity. winapp should be a vcpkg port that provides native CMake integration, replacing manual execute_process() calls with proper CMake functions and imported targets.

Goals

  1. Reduce C++ CMakeLists.txt from ~80 lines of boilerplate to ~15 lines
  2. Package resolution from winapp.yaml works automatically in CMake
  3. MSIX packaging is a build-system step (CMake custom target)
  4. Zero-friction first-time setup: vcpkg install winapp gets everything needed

Developer Experience: Before & After

Before (current — 79 lines in sample CMakeLists.txt)
cmake_minimum_required(VERSION 3.20) project(cpp-app) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 30 lines: download winapp CLI if not in PATH find_program(WINAPP_CLI winapp) if(NOT WINAPP_CLI) # ... download logic, architecture detection, zip extraction ... endif() # 10 lines: restore headers if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.winapp/include") execute_process(COMMAND "${WINAPP_CLI}" restore ...) endif() add_executable(cpp-app main.cpp) target_link_libraries(cpp-app PRIVATE WindowsApp.lib OneCoreUap.lib) target_include_directories(cpp-app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/.winapp/include) # Manually hardcode WIL path set(WIL_PACKAGE_DIR "$ENV{UserProfile}/.winapp/packages/Microsoft.Windows.ImplementationLibrary.1.0.260126.7") target_include_directories(cpp-app PRIVATE ${WIL_PACKAGE_DIR}/include/) # 8 lines: post-build debug identity add_custom_command(TARGET cpp-app POST_BUILD COMMAND $<$<CONFIG:Debug>:winapp> $<$<CONFIG:Debug>:create-debug-identity> ... )

After (with vcpkg port)

cmake_minimum_required(VERSION 3.20) project(cpp-app) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(winapp REQUIRED) add_executable(cpp-app main.cpp) # Links WindowsApp.lib + OneCoreUap.lib, sets include dirs for # all packages from winapp.yaml (WinAppSDK, WIL, etc.) target_link_libraries(cpp-app PRIVATE winapp::sdk) # Post-build: apply debug identity in Debug config winapp_debug_identity(cpp-app) # Build target: create MSIX package (cmake --build build --target package-cpp-app) winapp_package(cpp-app)

From ~80 lines to ~15 lines, with automatic package resolution and MSIX packaging as a proper build target.


Component 0: CLI Changes

winapp restore copies ALL package artifacts

Currently winapp restore copies Windows App SDK headers to .winapp/include and libs to .winapp/lib, but does NOT process other packages in winapp.yaml (e.g., WIL). Developers must manually locate and reference those packages.

Change: winapp restore processes ALL packages in winapp.yaml and copies their include headers and libraries into the unified .winapp/ directories:

.winapp/ ├── include/ │ ├── winrt/ # from Windows App SDK (existing) │ ├── wil/ # from WIL (NEW — auto-copied) │ ├── MddBootstrap.h # from Windows App SDK (existing) │ └── ... ├── lib/ │ └── ... └── bin/ └── ... 

A single target_include_directories(myapp PRIVATE .winapp/include) gives access to ALL packages — no per-package path resolution needed.

New --output-dir flag on winapp restore

# Current behavior (unchanged) winapp restore # → .winapp/ in cwd # New: explicit output directory winapp restore --output-dir build/_winapp # → build/_winapp/

The CMake module uses this to place SDK artifacts in the build tree (following CMake conventions that generated files belong in CMAKE_BINARY_DIR, not the source tree).

New outputDir field in winapp.yaml

outputDir: build/.winapp # optional, default: .winapp packages: - name: Microsoft.Windows.CppWinRT version: 2.0.250303.1 # ...

Precedence: --output-dir CLI flag > outputDir in yaml > default .winapp/

Updated winapp init flow for CMake projects

Currently winapp init creates config files AND restores SDKs. These should be separable. When CMakeLists.txt exists, winapp init automatically:

  • Skips SDK restore (CMake will handle it via find_package)
  • Creates vcpkg.json with winapp dependency
  • Creates vcpkg-configuration.json pointing to the winapp repo as a vcpkg registry
  • Parses CMakeLists.txt to find the target name (from add_executable)
  • Prompts the user to auto-edit CMakeLists.txt with the integration lines

Example output:

> winapp init ✓ Created appxmanifest.xml ✓ Created Assets/ ✓ Created winapp.yaml ✓ Created vcpkg.json ✓ Created vcpkg-configuration.json ✓ Updated .gitignore CMake project detected. Found target: cpp-app Would you like to update CMakeLists.txt with winapp integration? [Y/n] y ✓ Added find_package(winapp REQUIRED) ✓ Added target_link_libraries(cpp-app PRIVATE winapp::sdk) ✓ Added winapp_debug_identity(cpp-app) Configure with: cmake -B build -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake 

If the user declines, the lines to add are printed instead for manual editing.

Generated vcpkg files

vcpkg.json:

{ "dependencies": ["winapp"] }

vcpkg-configuration.json:

{ "registries": [ { "kind": "git", "repository": "https://github.com/microsoft/winappCli", "baseline": "<latest-commit-hash>", "packages": ["winapp"] } ] }

A --skip-restore flag is also available for explicit control: winapp init --skip-restore


Component 1: vcpkg Port

Port structure

ports/winapp/ ├── portfile.cmake # Downloads winapp CLI + installs CMake modules ├── vcpkg.json # Port manifest ├── winapp-config.cmake # find_package() entry point └── winapp-functions.cmake # CMake helper functions 

portfile.cmake behavior

  1. Downloads the winapp CLI binary (architecture-appropriate) from GitHub releases
  2. Installs it to ${CURRENT_PACKAGES_DIR}/tools/winapp/winapp.exe
  3. Installs winapp-config.cmake and winapp-functions.cmake to the CMake package config directory
  4. No C++ libraries are compiled — this is a "tool + CMake modules" port

What find_package(winapp) does

  1. Locates the winapp CLI (vcpkg tools dir → system PATH → auto-download fallback)
  2. Sets WINAPP_CLI cache variable pointing to the winapp executable
  3. Runs winapp restore --output-dir ${CMAKE_BINARY_DIR}/_winapp if headers don't exist — restores ALL packages to the build tree
  4. Creates imported targets pointing at ${CMAKE_BINARY_DIR}/_winapp/include and _winapp/lib
  5. Loads winapp-functions.cmake to make helper functions available

Note: Certificate generation is not automatic — developers run winapp cert generate manually when needed.


Component 2: CMake API

Imported Targets

Target What it provides
winapp::sdk Links WindowsApp.lib + OneCoreUap.lib, adds _winapp/include to include dirs for all packages from winapp.yaml, includes Windows App SDK libraries and bootstrap headers

Functions

winapp_debug_identity(<target>)

Adds a post-build command that applies debug identity (sparse package) in Debug configuration.

winapp_debug_identity(cpp-app)

winapp_package(<name> [TARGETS ...] [CERT <path>] [OUTPUT_DIR <dir>] [MANIFEST <path>])

Creates a custom build target package-<name> that builds an MSIX package.

Single-target (most common):

winapp_package(cpp-app) # cmake --build build --target package-cpp-app --config Release

Multi-target (app + DLLs):

winapp_package(myapp TARGETS myapp helper plugin CERT devcert.pfx ) # Collects myapp.exe + helper.dll + plugin.dll into a single MSIX

Parameters:

  • TARGETS — CMake targets to include in the MSIX (default: <name> only)
  • CERT — Path to signing certificate (optional; if omitted, MSIX is unsigned)
  • OUTPUT_DIR — Where to place the MSIX (default: ${CMAKE_BINARY_DIR}/msix)
  • MANIFEST — Path to appxmanifest.xml (default: ${CMAKE_CURRENT_SOURCE_DIR}/appxmanifest.xml)

winapp_resolve_package(<package_name> <output_variable>) (optional / advanced)

Resolves a raw package path from winapp.yaml into a CMake variable. Rarely needed since winapp restore copies all headers into the unified include directory.

winapp_resolve_package(Microsoft.Windows.ImplementationLibrary WIL_DIR)

winapp_restore()

Restores packages from winapp.yaml. Called automatically by find_package(winapp) but can be invoked explicitly.


Component 3: Enhanced winapp restore

Primary solution: winapp restore copies all package artifacts

#292 is primarily solved by making winapp restore process ALL packages — not just Windows App SDK. After restore, .winapp/include contains headers from every package, so a single include path covers everything.

Design Decisions

winapp.yaml stays as source of truth

  • vcpkg.json is only for declaring the winapp port dependency itself
  • Package versions (WindowsAppSDK, CppWinRT, WIL, etc.) remain in winapp.yaml
  • Consistent across C++, Rust, Electron, and other ecosystems

CLI version pinning and auto-download fallback

The CMake module defines version constraints:

  • WINAPP_MIN_CLI_VERSION — minimum acceptable version (e.g., 0.10.0) for system-installed CLI
  • WINAPP_DOWNLOAD_VERSION — exact version to auto-download if needed (e.g., 0.12.0)

find_package(winapp) resolution order:

  1. vcpkg tools dir → version pinned by portfile (SHA512 verified), always used
  2. System PATH → runs winapp --version, accepts if >= WINAPP_MIN_CLI_VERSION
  3. Auto-download → downloads WINAPP_DOWNLOAD_VERSION from GitHub releases (if PATH version too old or not found)

Generated artifacts go in build tree

  • SDK headers, libs, and binaries are restored to ${CMAKE_BINARY_DIR}/_winapp/
  • Source tree stays clean — no .winapp/ directory
  • Non-CMake workflows are unaffected — default remains .winapp/ in project root
  • --output-dir CLI flag overrides everything (used by CMake module)

Port distribution

  • Overlay port first (in winapp CLI repo under ports/), then submit to official vcpkg registry once stable
  • Independent port versioning — portfile pins specific CLI version via download URL + SHA512 hash

Files to Create / Modify

New files

  • ports/winapp/portfile.cmake — vcpkg portfile
  • ports/winapp/vcpkg.json — vcpkg manifest
  • ports/winapp/winapp-config.cmake — CMake find_package config
  • ports/winapp/winapp-functions.cmake — CMake helper functions

CLI changes

  • Enhance restore to copy artifacts from ALL packages in winapp.yaml
  • Add --output-dir flag to restore command
  • Add --skip-restore flag to init command
  • CMake auto-detection in init (parse target, generate vcpkg files, offer to edit CMakeLists.txt)
  • Add outputDir field support in winapp.yaml parsing
  • (Optional) Add resolve-package-path command for advanced use cases

Docs & samples

  • samples/cpp-app/CMakeLists.txt — Simplify to use find_package(winapp)
  • samples/cpp-app/vcpkg.json — Add vcpkg manifest to sample
  • docs/guides/cpp.md — Update guide with vcpkg workflow
  • docs/cmake.md - Documenting the cmake functions and usage

Verification

  1. Restore test: winapp restore copies headers from ALL packages (WIL, CppWinRT, etc.) into .winapp/include
  2. Integration test: vcpkg install winapp succeeds on x64 and ARM64
  3. End-to-end: Clone sample → cmake -B build -DCMAKE_TOOLCHAIN_FILE=<vcpkg> → build → verify debug identity works
  4. Packaging test: cmake --build build --target package-cpp-app --config Release produces valid MSIX
  5. Fallback test: Without vcpkg, find_package(winapp) auto-downloads CLI and still works
  6. Init test: winapp init in a CMake project creates vcpkg files and offers to edit CMakeLists.txt

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions