This document describes the cmake build for QuantLibXL. It exists alongside the hand-maintained Visual Studio solution files documented in README_vs_build.md; both builds use the same source files and produce the same XLL output.
| Property | Value |
|---|---|
| Platform | x64 and Win32 (x86) |
| Compilers | VS 2026 (v145 toolset), VS 2022 (v143 toolset) |
| Configurations | Release static, Debug static, Release dynamic, Debug dynamic |
| XLL output | `build<preset>\xll\QuantLibXL-[-x64]-mt[-s |
The platform tag -x64 is present only for x64 builds; Win32 builds omit it
(e.g. QuantLibXL-v145-mt-s-1_42_0.xll), matching the hand-maintained Win32
solution output name. Win32 requires a Boost built for x86 (see section 3).
The XLL is written into the cmake binary directory (build\<preset>\xll\),
which keeps it separate from the output of the hand-maintained solution
files (QuantLibXL\xll\).
The cmake build has the same prerequisites as the hand-maintained build (see README_vs_build.md sections 2.1 and 2.2) plus:
- CMake 3.15+ - included with the "C++ CMake tools" component of the VS "Desktop development with C++" workload.
Boost paths are not baked into the shared CMakePresets.json. Instead each user supplies them in a CMakeUserPresets.json file in the repo root. That file is listed in .gitignore and is never committed.
Create CMakeUserPresets.json by copying the template below and adjusting the paths to match your local Boost installation:
{
"version": 6,
"configurePresets": [
{
"name": "windows-vs2026-x64-dynamic",
"displayName": "VS 2026 x64 — dynamic CRT (Release+Debug)",
"inherits": "windows-vs2026-x64-dynamic-base",
"cacheVariables": {
"BOOST_INCLUDEDIR": "C:/path/to/boost/include",
"BOOST_LIBRARYDIR": "C:/path/to/boost/lib-md"
}
},
{
"name": "windows-vs2026-x64-static",
"displayName": "VS 2026 x64 — static CRT (Release+Debug)",
"inherits": "windows-vs2026-x64-static-base",
"cacheVariables": {
"BOOST_INCLUDEDIR": "C:/path/to/boost/include",
"BOOST_LIBRARYDIR": "C:/path/to/boost/lib-mt"
}
},
{
"name": "windows-vs2026-Win32-static",
"displayName": "VS 2026 Win32 — static CRT (Release+Debug)",
"inherits": "windows-vs2026-Win32-static-base",
"cacheVariables": {
"BOOST_INCLUDEDIR": "C:/path/to/boost/include",
"BOOST_LIBRARYDIR": "C:/path/to/boost/lib-mt-x86"
}
},
{
"name": "windows-vs2022-x64-dynamic",
"displayName": "VS 2022 x64 — dynamic CRT (Release+Debug)",
"inherits": "windows-vs2022-x64-dynamic-base",
"cacheVariables": {
"BOOST_INCLUDEDIR": "C:/path/to/boost/include",
"BOOST_LIBRARYDIR": "C:/path/to/boost/lib-md"
}
},
{
"name": "windows-vs2022-x64-static",
"displayName": "VS 2022 x64 — static CRT (Release+Debug)",
"inherits": "windows-vs2022-x64-static-base",
"cacheVariables": {
"BOOST_INCLUDEDIR": "C:/path/to/boost/include",
"BOOST_LIBRARYDIR": "C:/path/to/boost/lib-mt"
}
}
],
"buildPresets": [
{ "name": "windows-vs2026-x64-dynamic-release", "configurePreset": "windows-vs2026-x64-dynamic", "configuration": "Release" },
{ "name": "windows-vs2026-x64-dynamic-debug", "configurePreset": "windows-vs2026-x64-dynamic", "configuration": "Debug" },
{ "name": "windows-vs2026-x64-static-release", "configurePreset": "windows-vs2026-x64-static", "configuration": "Release" },
{ "name": "windows-vs2026-x64-static-debug", "configurePreset": "windows-vs2026-x64-static", "configuration": "Debug" },
{ "name": "windows-vs2026-Win32-static-release","configurePreset": "windows-vs2026-Win32-static","configuration": "Release" },
{ "name": "windows-vs2026-Win32-static-debug", "configurePreset": "windows-vs2026-Win32-static","configuration": "Debug" },
{ "name": "windows-vs2022-x64-dynamic-release", "configurePreset": "windows-vs2022-x64-dynamic", "configuration": "Release" },
{ "name": "windows-vs2022-x64-dynamic-debug", "configurePreset": "windows-vs2022-x64-dynamic", "configuration": "Debug" },
{ "name": "windows-vs2022-x64-static-release", "configurePreset": "windows-vs2022-x64-static", "configuration": "Release" },
{ "name": "windows-vs2022-x64-static-debug", "configurePreset": "windows-vs2022-x64-static", "configuration": "Debug" }
]
}If Boost is installed in a location that cmake can find automatically (e.g. via BOOST_ROOT or vcpkg), CMakeUserPresets.json can simply be omitted and the shared presets will work as-is.
Configure presets are provided per compiler x platform x CRT combination:
| Configure preset | Compiler | Platform | CRT |
|---|---|---|---|
windows-vs2026-x64-dynamic |
VS 2026 | x64 | /MD |
windows-vs2026-x64-static |
VS 2026 | x64 | /MT |
windows-vs2026-Win32-static |
VS 2026 | Win32 | /MT |
windows-vs2022-x64-dynamic |
VS 2022 | x64 | /MD |
windows-vs2022-x64-static |
VS 2022 | x64 | /MT |
The shared CMakePresets.json also defines the hidden base presets
windows-vs2026-Win32-base and windows-vs2026-Win32-static-base that the
Win32 user preset inherits from. Each configure preset has a corresponding pair
of build presets with -release and -debug suffixes.
After creating CMakeUserPresets.json (see section 3), run cmake from the root of
your clone. In the commands below, replace <repo> with the full path of that
clone (the folder that contains QuantLibXL):
# VS 2026, static CRT
cmake --preset windows-vs2026-x64-static -S <repo> -B <repo>\build\windows-vs2026-x64-static
# VS 2026, dynamic CRT
cmake --preset windows-vs2026-x64-dynamic -S <repo> -B <repo>\build\windows-vs2026-x64-dynamic
# VS 2022, static CRT
cmake --preset windows-vs2022-x64-static -S <repo> -B <repo>\build\windows-vs2022-x64-static
# VS 2022, dynamic CRT
cmake --preset windows-vs2022-x64-dynamic -S <repo> -B <repo>\build\windows-vs2022-x64-dynamic
# VS 2026, Win32 (x86), static CRT
cmake --preset windows-vs2026-Win32-static -S <repo> -B <repo>\build\windows-vs2026-Win32-staticPass --config and --target QuantLibXL to build a specific configuration.
Examples using the VS 2026 static preset:
# Release static -> QuantLibXL-v145-x64-mt-s-1_42_0.xll
cmake --build <repo>\build\windows-vs2026-x64-static --config Release --target QuantLibXL
# Debug static -> QuantLibXL-v145-x64-mt-sgd-1_42_0.xll
cmake --build <repo>\build\windows-vs2026-x64-static --config Debug --target QuantLibXLExamples using the VS 2022 static preset:
# Release static -> QuantLibXL-v143-x64-mt-s-1_42_0.xll
cmake --build <repo>\build\windows-vs2022-x64-static --config Release --target QuantLibXL
# Debug static -> QuantLibXL-v143-x64-mt-sgd-1_42_0.xll
cmake --build <repo>\build\windows-vs2022-x64-static --config Debug --target QuantLibXLExample using the VS 2026 Win32 static preset (note the name has no -x64 tag):
# Release static -> QuantLibXL-v145-mt-s-1_42_0.xll
cmake --build <repo>\build\windows-vs2026-Win32-static --config Release --target QuantLibXL| Preset | Config | XLL filename |
|---|---|---|
| windows-vs2026-x64-static | Release | build\windows-vs2026-x64-static\xll\QuantLibXL-v145-x64-mt-s-1_42_0.xll |
| windows-vs2026-x64-static | Debug | build\windows-vs2026-x64-static\xll\QuantLibXL-v145-x64-mt-sgd-1_42_0.xll |
| windows-vs2026-x64-dynamic | Release | build\windows-vs2026-x64-dynamic\xll\QuantLibXL-v145-x64-mt-1_42_0.xll |
| windows-vs2026-x64-dynamic | Debug | build\windows-vs2026-x64-dynamic\xll\QuantLibXL-v145-x64-mt-gd-1_42_0.xll |
| windows-vs2022-x64-static | Release | build\windows-vs2022-x64-static\xll\QuantLibXL-v143-x64-mt-s-1_42_0.xll |
| windows-vs2022-x64-static | Debug | build\windows-vs2022-x64-static\xll\QuantLibXL-v143-x64-mt-sgd-1_42_0.xll |
| windows-vs2022-x64-dynamic | Release | build\windows-vs2022-x64-dynamic\xll\QuantLibXL-v143-x64-mt-1_42_0.xll |
| windows-vs2022-x64-dynamic | Debug | build\windows-vs2022-x64-dynamic\xll\QuantLibXL-v143-x64-mt-gd-1_42_0.xll |
| windows-vs2026-Win32-static | Release | build\windows-vs2026-Win32-static\xll\QuantLibXL-v145-mt-s-1_42_0.xll |
| windows-vs2026-Win32-static | Debug | build\windows-vs2026-Win32-static\xll\QuantLibXL-v145-mt-sgd-1_42_0.xll |
CMakeLists.txt <- root: wires together all subprojects
CMakePresets.json <- configure and build presets
gensrc\
CMakeLists.txt <- optional gensrc code-generation step
cmake\
commonSettings.cmake <- MSVC compile options, CRT selection (generic)
DocsCommon.cmake <- shared docs helper (tool discovery + target helpers)
RunDoxygen.cmake <- build-time driver that runs doxygen for one project
Docs\CMakeLists.txt <- gensrc-docs target
ObjectHandler\
CMakeLists.txt <- builds xlsdk, ohlib, ohxllib static libs
Docs\CMakeLists.txt <- ObjectHandler-docs target
QuantLibAddin\
CMakeLists.txt <- builds QuantLibObjects static lib
Docs\CMakeLists.txt <- QuantLibAddin-docs target
QuantLibXL\
CMakeLists.txt <- builds the XLL
Docs\CMakeLists.txt <- QuantLibXL-docs target
QuantLib\
CMakeLists.txt <- upstream QuantLib cmake (unchanged)
The shared cmake helpers live under gensrc\cmake\ because gensrc is the root of
the project dependency chain (gensrc <- ObjectHandler <- QuantLibAddin <- QuantLibXL): every project may depend on gensrc, so generic build code placed
there is visible to all of them without any of them having to reference a project
further down the chain. In particular, ObjectHandler can be configured and built
standalone (see section 10) without any reference to QuantLibAddin, QuantLib or
QuantLibXL.
Each subproject keeps its own documentation build in its Docs\CMakeLists.txt,
so a project's docs are part of that standalone project (just like its code) and
can be built on their own. The shared docs pieces are DocsCommon.cmake (tool
discovery and the add_doxygen_docs / add_gensrc_docs helpers) and
RunDoxygen.cmake (the per-project driver), both under gensrc\cmake\.
Because every subproject's CMakeLists.txt guards its root-only logic with
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR), a subproject can be
configured on its own, without the projects further down the dependency chain.
For example, ObjectHandler (which depends only on its upstream gensrc and on
Boost) can be built standalone — with no reference to QuantLibAddin, QuantLib or
QuantLibXL:
# Just the ObjectHandler libraries
cmake -S ObjectHandler -B build\objecthandler-only ^
-DBOOST_INCLUDEDIR=C:/path/to/boost/include ^
-DBOOST_LIBRARYDIR=C:/path/to/boost/lib-md
cmake --build build\objecthandler-only --config Release
# ObjectHandler plus its documentation (needs Python + Doxygen, not Boost)
cmake -S ObjectHandler -B build\objecthandler-docs -DBUILD_DOCS=ON
cmake --build build\objecthandler-docs --target docsThis mirrors Build A, where ObjectHandler\ObjectHandler.sln builds a
standalone ObjectHandler.
The ObjectHandler and QuantLibAddin sources include Boost-style auto-link
headers (oh/auto_link.hpp, qlo/auto_link.hpp, xlsdk/auto_link.hpp)
that emit #pragma comment(lib, ...) directives referencing tagged library
names (e.g. QuantLibObjects-v145-x64-mt-s-1_42_0.lib). These conflict
with the cmake-managed link step.
Each auto_link header has been guarded with a #ifndef macro:
| Header | Guard macro | Defined by (PUBLIC, on) |
|---|---|---|
oh/auto_link.hpp |
OH_NO_AUTO_LINK |
ohlib |
qlo/auto_link.hpp |
QLADDIN_NO_AUTO_LINK |
QuantLibObjects |
xlsdk/auto_link.hpp |
XLSDK_NO_AUTO_LINK |
xlsdk |
Each macro is defined as a PUBLIC compile definition on the library that owns
the header (via target_compile_definitions), so it is inherited by every
consumer of that library through the normal link graph and never leaks the other
projects' names into a project's build. In particular, the ObjectHandler build
defines only OH_NO_AUTO_LINK and XLSDK_NO_AUTO_LINK; it has no knowledge of
QLADDIN_NO_AUTO_LINK (that belongs to QuantLibAddin), respecting the project
hierarchy. The hand-maintained solution files do not define these macros, so
their behaviour is unchanged.
QuantLib's cmake QL_TAGGED_LAYOUT option, when ON, sets
CMAKE_RELEASE_POSTFIX and CMAKE_DEBUG_POSTFIX globally, which would
apply the tagged suffix to every cmake target in the build. To avoid
this, QL_TAGGED_LAYOUT is set to OFF in the root CMakeLists.txt and the
postfix is applied only to ql_library via set_target_properties.
The XLL OUTPUT_NAME uses cmake generator expressions to select the
correct runtime tag per configuration:
QuantLibXL-<toolset>[-x64]-<runtime-tag>-1_42_0.xll
<toolset> is derived from MSVC_TOOLSET_VERSION at configure time
(e.g. v145 for VS 2026, v143 for VS 2022). The -x64 platform tag is
added only for 64-bit builds (CMAKE_SIZEOF_VOID_P EQUAL 8) and omitted for
Win32, matching the hand-maintained solution's Win32 output name.
<runtime-tag> is -mt-s / -mt-sgd (static CRT) or -mt / -mt-gd
(dynamic CRT) depending on MSVC_LINK_DYNAMIC_RUNTIME.
The cmake build can also build the HTML documentation for gensrc,
ObjectHandler, QuantLibAddin and QuantLibXL, replicating the legacy automake
make docs target. This works on both Windows and Linux/WSL.
- Doxygen (the
doxygenexecutable onPATH). - Graphviz (the
dotexecutable onPATH) — optional, but enables the inheritance / collaboration diagrams. Whendotis not found the docs still build, just without the graphs. - Python 3 — used to run the doxyfile preprocessor and
gensrc.
A C++ compiler and Boost are not required to build the documentation.
There are two ways to drive the documentation build.
A. Standalone (recommended when you only want the docs) — each project's
Docs directory is itself a tiny standalone cmake project that needs only
Python, Doxygen and dot, so it works even on a machine that cannot compile the
libraries (no Boost / no C++ toolchain). Configure the Docs directory of the
project you want and build its docs target:
cmake -S ObjectHandler\Docs -B build\docs-objecthandler
cmake --build build\docs-objecthandler --target docsThe same pattern works for gensrc\Docs, QuantLibAddin\Docs and
QuantLibXL\Docs. (The QuantLibXL library is Windows-only, but its
documentation builds on any platform.)
B. As part of the main build — add -DBUILD_DOCS=ON at configure
time, then build the aggregate docs target (or an individual project target):
cmake --preset windows-vs2026-x64-static -DBUILD_DOCS=ON
cmake --build build\windows-vs2026-x64-static --target docsOn Linux/WSL the equivalent standalone build is:
cmake -S ObjectHandler/Docs -B build/docs-objecthandler
cmake --build build/docs-objecthandler --target docsIn the main build (option B) the aggregate docs target and all four
per-project targets are available:
| Target | Builds |
|---|---|
docs |
all four projects below (aggregate) |
gensrc-docs |
gensrc HTML docs |
ObjectHandler-docs |
ObjectHandler HTML docs |
QuantLibAddin-docs |
QuantLibAddin HTML docs |
QuantLibXL-docs |
QuantLibXL HTML docs |
In a standalone Docs build (option A) the project exposes its own
<project>-docs target plus a convenience docs target that builds it.
Because this is a pristine checkout, the Docs/auto.pages Doxygen inputs for
ObjectHandler and QuantLibAddin do not exist yet. Each project that needs them
defines its own gensrc step — ObjectHandler-docs-gensrc and
QuantLibAddin-docs-gensrc — which runs gensrc.py -d and is wired as a
prerequisite of that project's doc target. QuantLibXL reuses
QuantLibAddin-docs-gensrc (it consumes the same QuantLibAddin auto.pages), so
a single --target docs produces everything from a clean tree.
The generated HTML is written under the build tree, keeping the source tree clean. In the main build (option B) all four appear under one build directory:
build\<preset>\gensrc\Docs\gensrc-docs\html\index.html
build\<preset>\ObjectHandler\Docs\ObjectHandler-docs\html\index.html
build\<preset>\QuantLibAddin\Docs\QuantLibAddin-docs\html\index.html
build\<preset>\QuantLibXL\Docs\QuantLibXL-docs\html\index.html
In a standalone build (option A) the output is under that project's build
directory, e.g. build\docs-objecthandler\ObjectHandler-docs\html\index.html.
A per-project doxywarnings.txt is written next to each html directory.
- Each subproject owns its documentation build in its own
<project>\Docs\CMakeLists.txt, consistent with the repository convention that every subdirectory is a self-contained project. Two pieces are shared undergensrc\cmake\(gensrc is the upstream of every project, so generic helpers placed there are visible to all without creating a downstream dependency):gensrc\cmake\DocsCommon.cmakediscovers the tools (Python 3, Doxygen, optional dot) once and provides theadd_doxygen_docsandadd_gensrc_docshelper functions used by eachDocs\CMakeLists.txt.gensrc\cmake\RunDoxygen.cmakeis acmake -Pscript invoked once per project. It runsObjectHandler/Docs/preprocess_doxyfile.py(which setsSTRIP_FROM_PATHand, on posix,HAVE_DOT=YES/GENERATE_HTMLHELP=NO), appends a small block of overrides that redirectOUTPUT_DIRECTORYinto the build tree, runs doxygen with the working directory set to the project'sDocsdirectory (the.doxyfiles use relative INPUT / header paths), and finally copiestabs.css,ql.cssand theimagesinto the html output — exactly the steps the oldMakefile.amperformed.
- The older
.doxyfiles predate Doxygen 1.9, so Doxygen emits a number of "obsolete tag" warnings; these are harmless.WARN_AS_ERRORisNO, so content warnings do not fail the build.
The workflow .github/workflows/build-xll.yml builds the static-CRT Release
XLL for both x64 and Win32 on a GitHub-hosted windows-latest runner,
bundles them with the example spreadsheets into a versioned package, and can
optionally publish that package as a GitHub Release.
| Property | Value |
|---|---|
| Trigger | workflow_dispatch (manual) only |
| Runner | windows-latest (Visual Studio 2026, toolset v145) |
| Variants | static-CRT Release, x64 and Win32 (matrix) |
| Inputs | quantlib_ref (default master), boost_version (default 1.89.0), arch (both/x64/Win32, default both), make_release (default false), release_tag (default v1.42.0) |
| Output | per-arch XLL artifacts, a QuantLibXL-<version> package artifact, and (optionally) a GitHub Release |
The runner uses VS 2026, so the CI XLLs are tagged v145
(QuantLibXL-v145-x64-mt-s-1_42_0.xll and QuantLibXL-v145-mt-s-1_42_0.xll),
matching a local VS 2026 build.
windows-latest maps to the VS 2026 (v145) image, which Boost 1.89's build
tooling does not yet know about, so the workflow drives it explicitly:
bootstrap.bat msvcbuilds theb2engine usingcl.exedirectly (its auto-detection only recognises up tovc143and otherwise fails with "Unknown toolset: vcunk").- A generated
user-config.jamregisters the toolset under the known version14.3, but points both the compiler path and the<setup>script at the real v145cl.exeandvcvarsall.bat(located viavswhere). Without the explicit<setup>,b2cannot build themsvc-setuptarget and silently skips every object file.
The resulting Boost libraries carry a -vc143- filename tag (a label only —
they are genuinely compiled by the v145 compiler). The XLL itself is compiled
by CMake with the real v145 toolset, so the v145 artifact name is accurate.
After both build legs succeed, a package job assembles a versioned tree and
uploads it as the QuantLibXL-<version> artifact (itself a zip). It extracts
to a single rooted folder:
QuantLibXL-1.42.0/
Addins/
x64/ QuantLibXL-v145-x64-mt-s-1_42_0.xll
Win32/ QuantLibXL-v145-mt-s-1_42_0.xll
Examples/ InterestRateDerivatives.xlsx, YieldCurveBootstrapping.xlsx
The x64 and Win32 XLLs are placed in separate Addins/ subfolders so end users
must consciously pick the one matching their Excel bitness.
When the make_release input is checked, the same job zips that tree into
QuantLibXL-<version>.zip and publishes a GitHub Release with
softprops/action-gh-release. The release step:
- requires
arch=both(so the package contains both XLLs) and arelease_tag; - creates the tag itself from the commit the workflow ran on — you do not push a tag manually;
- attaches the zip as a publicly downloadable asset that never expires.
Standard release flow: merge the CI branch into the default branch, then on
Actions → Build QuantLibXL XLL → Run workflow choose Use workflow from:
main, tick make_release, set release_tag (e.g. v1.42.0) and run. The
release is cut from main, and the tag marks the exact commit that produced the
artifacts. Re-running with the same tag updates the existing release.
Publishing a release requires the workflow's contents: write permission (set
in the workflow) and the repository's Settings → Actions → General → Workflow
permissions to allow read/write.
The workflow is self-contained and needs no CMakeUserPresets.json. Each
matrix leg:
- Clones QuantLib from
https://github.com/<owner>/QuantLibwhere<owner>is the owner of the repository running the workflow, so the QuantLib fork is taken from the same owner as this repository. The sources are placed in the camel-caseQuantLib\folder the build expects (QuantLib is.gitignore'd and never committed here). - Builds Boost (from source, only the required components, static runtime)
for the leg's architecture — x64 staged to
boost\stage\lib, Win32 staged toboost\stage32\liband built from an x86 developer environment. The result is cached withactions/cachekeyed on version + architecture, so the slow Boost build happens only on the first run. - Configures and builds with
-DRUN_GENSRC=ON(a Full build; the generated sources are.gitignore'd) passingBOOST_INCLUDEDIR/BOOST_LIBRARYDIRon the cmake command line, then builds theQuantLibXLtarget inRelease. - Verifies and uploads the expected XLL, failing the job if it is missing.