Skip to content

Summary of Changes CMake Library Git Module

Summary of modifications since commit a9c278edb9296e26a071dad78daa687bd30baf5a in the cmake/lib git submodule. Three commits were made between 2026-10-17 and 2026-10-19.


Dynamic Subcommand Loader

Files: bin/build.py, bin/build-demo.py (new)

The Problem

The main build.py script was growing monolithic. Every new subcommand (like demo, docker, wine, publish) had to be hardcoded inside build.py itself, making the script harder to maintain and extending it required modifying the core build infrastructure.

The Solution

Introduced _load_subcommand_modules() in bin/build.py, a function that dynamically discovers and imports all build-*.py files at runtime using importlib.util. Each subcommand module defines and registers a SubCommand subclass, keeping subcommand logic fully decoupled from the main script.

Key design decisions:

  • Avoid re-importing build.py: Subcommand modules must use sys.modules["__main__"] or sys.modules["build"] to reference the already-loaded main module, rather than from build import ..., which would re-execute module-level code and create duplicate registries.
  • Wine symlink resolution: Under Wine, os.path.realpath() does not resolve Linux symlinks, so the loader searches both the script's directory and an sbin/ subdirectory to find subcommand modules regardless of how build.py was invoked.
  • Deduplication: A seen_files set prevents the same module from being loaded twice when found in multiple search directories.

bin/build-demo.py serves as the first example subcommand, demonstrating the pattern for future extensions.


CMake Execution Profiling

Files: bin/build.py

The Problem

When a CMake configure or build step takes an unexpectedly long time, there was no built-in way to identify which target, source file, or CMake script is causing the slowdown. Developers had to resort to external profiling tools or manual timing.

The Solution

Added a --profile flag to the native subcommand in bin/build.py. When enabled, it passes CMake's native profiling flags to both the configure and build steps:

  • Configure: --profiling-format=google-trace --profiling-output=profile-make.json
  • Build: --profiling-format=google-trace --profiling-output=profile-build.json

The output is in Google Trace format, viewable in chrome://tracing or about:tracing in Firefox, providing a visual timeline of all CMake operations. This makes it trivial to identify slow targets, expensive CMake scripts, or bottlenecks in the build graph.


MSVC Debug Information Format

Files: SfBaseConfig.cmake

The Problem

When building with MSVC (including via Wine), the default debug information format produces separate .pdb files. For RelWithDebInfo and Debug build types, this can cause issues with certain debuggers and deployment scenarios where the .pdb file must be co-located with the binary.

The Solution

Added MSVC_DEBUG_INFORMATION_FORMAT property set to Embedded for both RelWithDebInfo and Debug build types in SfBaseConfig.cmake. This embeds the debug symbols directly into the binary, eliminating the need for separate .pdb files and simplifying debugging workflows, especially in Wine environments where file path mapping between Linux and Windows can complicate .pdb discovery.


Files: bin/build.py

The Problem

The wine subcommand in bin/build.py contained a workaround that created a symlink from the Wine user's Temp directory to the project's cmake-build directory. This was needed because Wine's TEMP and TMP environment variables were not properly set in the Docker image, causing temporary files to be created in locations outside the Docker mount and potentially lost.

The Root Cause

The underlying issue was in the Docker image setup (external/docker-runner repository). Two problems existed:

  1. Registry key formatting error: In builder/cpp.Dockerfile, the Windows registry entries for TEMP and TMP were incorrectly formatted. The keys were written as "TEMP=Z:\\tmp" and "TMP=Z:\\tmp", which is invalid registry syntax. The correct format requires double quotes around the key name: "TEMP"="Z:\\tmp" and "TMP"="Z:\\tmp". Without proper quoting, Wine never read these environment variables, leaving TEMP and TMP undefined or pointing to default locations.

  2. Missing Temp directory symlink: The Wine user's Temp directory at ${WINEPREFIX}/drive_c/users/user/Temp was not linked to a persistent location, causing temporary files to be lost when the Wine prefix was recreated.

The Solution

The fix was made in the external/docker-runner repository (commit a28cc8b):

  1. Fixed registry key formatting: Changed "TEMP=Z:\\tmp" to "TEMP"="Z:\\tmp" and "TMP=Z:\\tmp" to "TMP"="Z:\\tmp" in the Windows registry import script within builder/cpp.Dockerfile.

  2. Added Temp directory symlink: Added ln -s /tmp "${WINEPREFIX}/drive_c/users/user/Temp" to link the Wine user's Temp directory to the host's /tmp directory, ensuring temporary files persist and are accessible.

With these fixes in place, the workaround in bin/build.py that manually created the symlink was no longer needed and was removed. Removing dead workarounds reduces maintenance burden and potential side effects.


Qt Version Integration in Packaging

Files: SfBaseConfig.cmake, tpl/cpack/prepare.cmake, tpl/cpack/pkg-app.cmake

The Problem

When a project produces multiple packages — some depending on Qt and others not — there was no way to distinguish Qt-dependent packages from non-Qt packages by name alone. This made it difficult to trace which package version corresponds to which Qt version, especially when maintaining multiple Qt version builds.

The Solution

The mechanism works in three steps:

  1. Flag the target: In SfBaseConfig.cmake, the Sf_AddTarget() function accepts a FLAGS parameter (line 790) that can include qt as a flag. For example:

    Sf_AddTarget(myapp FLAGS qt VERSION)
    
    This stores the flag list in the target's SF_FLAGS property (line 813). The helper function Sf_HasTargetFlag() (line 841) can then check if a given target has a specific flag set.

  2. Build the Qt components list: In https://git.scanframe.com/library/cmake-lib/-/blob/main/tpl/cpack/prepare.cmake, each executable target is checked for the qt flag (line 148). If found, the component name is added to the SF_QT_COMPONENTS list (line 152). This list is then available to all CPack generator configurations.

  3. Append Qt version to package names: In tpl/cpack/pkg-app.cmake, each generator checks if the component is in SF_QT_COMPONENTS, and if so, appends -qt${SF_QT_VERSION} to the package filename:

    • RPM (line 22): CPACK_RPM_${_ComponentUpper}_PACKAGE_NAME gets -qt${SF_QT_VERSION} appended.
    • DEB (line 47): CPACK_DEBIAN_${_ComponentUpper}_PACKAGE_NAME gets -qt${SF_QT_VERSION} appended.
    • Archive (line 137): filename gets -qt${SF_QT_VERSION} appended.
    • NSIS (line 184): CPACK_PACKAGE_FILE_NAME gets -qt${SF_QT_VERSION} appended if any component uses Qt.
    • AppImage (line 200): CPACK_PACKAGE_FILE_NAME gets -qt${SF_QT_VERSION} appended if SF_QT_COMPONENTS is not empty.

For example, a package that was previously named myapp-gnu13.2-1.0.0 now becomes myapp-gnu13.2-qt6.10.3-1.0.0 when the target is flagged with qt and Qt 6.10.3 is used, making the Qt dependency and version explicit in the package name.


Wine Version Detection

Files: tpl/root/src/hwl/inc/hwl/hello.h, tpl/root/src/hwl/src/hello.cpp, tpl/root/src/cli/main.cpp

The Problem

The isWine() function in the helper library called GetModuleHandleA and GetProcAddress on every invocation, repeatedly querying ntdll.dll for the wine_get_version symbol. Additionally, there was no way to retrieve the actual Wine version string — only a boolean "running under Wine or not" check existed.

The Solution

Two improvements in tpl/root/src/hwl/src/hello.cpp:

  1. Cached detection: isWine() now stores the result in a static const bool, performing the GetModuleHandleW/GetProcAddress lookup only once. The function also switched from GetModuleHandleA to GetModuleHandleW for proper Unicode support.
  2. Version retrieval: A new function getWineVersion() was added that returns the Wine version string by calling wine_get_version(). The result is also cached via a sentinel pattern (UINTPTR_MAX) to avoid repeated GetProcAddress calls.

The template application tpl/root/src/cli/main.cpp was updated to display the Wine version string instead of the boolean "Yes/No" indicator.


Refined Output File Naming Conventions

Files: tpl/cpack/pkg-app.cmake

The Problem

Archive and installer filenames had inconsistent structure. The toolchain string was concatenated with a + separator inside a list element (e.g., win+gnu), making the filename harder to parse programmatically. Additionally, the architecture naming differed between Windows (x86_64) and Linux (x86-64) in ways that weren't intentional.

The Solution

Refactored filename construction in tpl/cpack/pkg-app.cmake to use consistent string concatenation instead of list-based assembly. The toolchain string is now appended directly without the + prefix inside list elements. For Windows archives, SF_ARCHITECTURE is used directly instead of SF_ARCHITECTURE_SAFE, preserving the native architecture name.


Build Script Logging Improvements

Files: bin/build.py

The Problem

When uploading packages to Nexus, the log output only showed filenames without sizes. For large packages, this made it hard to verify whether an upload completed correctly or to compare expected vs actual package sizes.

The Solution

Added get_readable_file_size() static method to bin/build.py that converts byte sizes to human-readable format (B, KiB, MiB, GiB, TiB). The Nexus upload log messages now include the file size in parentheses, e.g., Uploading APT repo file: package.deb (15.23 MiB).


CPack Configuration Enhancements

Files: tpl/cpack/pkg-qt-lib.cmake, tpl/cpack/project.cmake

The Problem

For Qt library DEB packages, the description was only set in CPACK_DEBIAN_*_DESCRIPTION, but not in CPACK_DEBIAN_*_DESCRIPTION_SUMMARY. This meant package managers showing only the summary line would display no description. Additionally, CPACK_PACKAGE_DESCRIPTION_SUMMARY was leaking into Qt component packages with unwanted shared text.

The Solution

  • In tpl/cpack/pkg-qt-lib.cmake, both _DESCRIPTION and _DESCRIPTION_SUMMARY are now set from the same _description variable, ensuring consistency.
  • In tpl/cpack/project.cmake, CPACK_PACKAGE_DESCRIPTION_SUMMARY is cleared for Qt component packages to prevent unwanted inherited descriptions.
  • The SF_ZIP_MANIFEST_FILE status message is now only printed on Windows, where it's relevant.
  • ShowAllVars() is now enabled (uncommented) for debugging CPack configuration issues.

Google Test MinGW Linking Fix

Files: SfGoogleTestConfig.cmake

The Problem

The Windows MinGW compiler produces linking errors when building GoogleTest-based tests:

  • undefined reference to '__imp__Exit'
  • undefined reference to '__imp_quick_exit'

This issue is documented in the project's own src/CMakeLists.txt, which has a comment above the GoogleTest subdirectory noting the problem.

The Root Cause

The issue stems from libmingw32.a's UCRT-only _Exit/quick_exit wrappers when using msvcrt toolchains. The symbols are not properly resolved during linking because mingwex (which provides the implementations) is not linked after gtest on the link line.

The Solution

Added a conditional workaround at the end of SfGoogleTestConfig.cmake:

if (SF_COMPILER STREQUAL "mingw")
    target_link_libraries(gtest INTERFACE mingwex)
endif ()

The INTERFACE keyword is critical — it ensures mingwex is emitted after gtest on the link line, which is required for the symbols to resolve correctly. This centralized fix in the CMake library means all projects using this library automatically get the workaround.


Template Project Updates

Files: tpl/root/AGENTS.md, tpl/root/CMakePresets.json, tpl/root/src/qt/CMakeLists.txt, tpl/root/src/tests/google/CMakeLists.txt

AGENTS.md Reorganization

The template AGENTS.md was restructured:

  • Added a Response Rules section at the top to guide AI agents working on projects using this template.
  • Reordered sections: Response Rules → Commands → Boundaries → C++ Code Style → Testing Strategy → Commit Style → Review of Code.
  • Removed the basic "Build the Project" and "Running Unit Tests" sections that were redundant with the build script's own help output.
  • Added a Review of Code section linking to the reviewer documentation.
  • Fixed indentation consistency (tabs vs spaces).

CMakePresets.json Generator Order

In CMakePresets.json, the CPack generator order for Linux presets was changed from [TGZ, AppImage, DEB] to [TGZ, DEB, AppImage].

The Problem

When packaging the Qt library, the AppImage generator fails and stops the CPack build, preventing DEB packages from being generated. Since Qt library publishing is currently a manual process, this is an acceptable limitation, but it means DEB packages — which are the primary automated output — should be generated first.

The Solution

AppImage was moved to the last position in the generator list. This way, TGZ and DEB packages are always produced before AppImage is attempted. If AppImage fails, the DEB package is already available for manual publishing. A lazy but practical workaround until Qt library AppImage support is properly fixed.