wxWidgets + CMake: Multiplatform Superbuild
By Luke
In this article I’ll show you how to create a native-looking UI application in C++ and build it on each of the major platforms (Windows, Linux, Mac OS) using a single build script code.
You can download the full source code of the sample program from github: https://github.com/lszl84/wx_cmake_template . Also, check the YouTube video below, which describes the process in detail, showing how this works on each of the aforementioned platforms:
Objectives
We’d like to automatize the building of a UI application written in C++ so that:
- it can be built on Windows, Mac OS and Linux using the same set of commands,
- the build script automatically detects the environment, compiler, etc. to use for compiling the app,
- the script checks if the UI library (wxWidgets in this case) is already installed in the operating system. If not found, the script downloads it from github, compiles and installs the library in a temporary directory containing build artifacts.
In this way, we can greatly simplify the build process for each platform, while also making the dependency management easier and more flexible. We save the user the hassle of downloading and installing necessary libraries by automatizing the process, while also giving them an option to link against a preinstalled copy of the library, if they so choose (e.g. when using our project as part of a larger system which already depends on the same libraries).
Tools and libraries used
wxWidgets
When it comes to multiplatform UI libraries for C++, the options are limited. Two solutions seem to be currently leading in the industry: Qt and wxWidgets. While Qt seems to be more powerful, its major disadvantage is the licensing process - users of the library need to purchase a commercial license in order to use Qt in their closed-source products. WxWidgets does not have this limitation, therefore it may be appealing to a broader user base.
CMake
CMake is a build system generator which is the de-facto standard for C++ development. While the syntax of CMake may look dated (or even ancient), and the system itself is quite complicated, the newer addition of ExternalProject and FetchContent functionalities aim to modernize the build system and make modern dependency management much easier.
We’ll see how to use ExternalProject to manage dependencies: find them in the system, and if that fails, automatically download from GitHub and build.
The code
Full source for this sample project can be downloaded from github: https://github.com/lszl84/wx_cmake_template . Here we’ll go through the main parts of the build script code, explaining how the parts fit together.
Let’s take a look at the directory structure of the sample project:

Project directory hierarchy
The CMakeLists.txt files are the build scripts used by the CMake build system. As you can see, there are a bunch of these in here:
- the root directory - this is the main build script, the starting point for the build process, which includes all the other scripts,
- the src directory - the build script for the main project,
- the thirdparty and thirdparty/wxwidgets directories - the purpose of these scripts is to find or download and build the wxWidgets library.
From an architecture standpoint, we’ll be using two ExternalProjects. Think about ExternalProject as a CMake’s way to specify a package, complete with the source directory (or URL to download the code), build commands etc.
Here’s the build script architecture:
- wxWidgets_external is the first of our ExternalProjects. It’s defined in the thirdparty/wxwidgets directory,
- wx_cmake_template_core is the main ExternalProject, defined in the root CMakeLists.txt with its source files in the src directory. It depends on wxWidgets_external, to make sure that the wxWidgets library is found in the system or downloaded and installed, before the compilation of the main wx_cmake_template_core project begins.
wxWidgets_external
Let’s take a look at thirdparty/wxwidgets/CMakeLists.txt:
find_package(wxWidgets QUIET)
if (wxWidgets_FOUND)
message(STATUS "Found preinstalled wxWidgets libraries at ${wxWidgets_LIBRARIES}")
add_library(wxWidgets_external INTERFACE)
else()
message(STATUS "Preinstalled wxWidgets not found.")
message(STATUS "Will download and install wxWidgets in ${STAGED_INSTALL_PREFIX}")
include(ExternalProject)
ExternalProject_Add(wxWidgets_external
GIT_REPOSITORY
https://github.com/wxWidgets/wxWidgets.git
GIT_TAG
master
UPDATE_COMMAND
""
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${STAGED_INSTALL_PREFIX}
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}
-DwxBUILD_SHARED=OFF
CMAKE_CACHE_ARGS
-DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
TEST_AFTER_INSTALL
0
DOWNLOAD_NO_PROGRESS
1
LOG_CONFIGURE
1
LOG_BUILD
1
LOG_INSTALL
1
)
set(wxWidgets_ROOT_DIR ${STAGED_INSTALL_PREFIX})
file(TO_NATIVE_PATH "${wxWidgets_ROOT_DIR}" wxWidgets_ROOT_DIR)
set(wxWidgets_ROOT_DIR ${wxWidgets_ROOT_DIR} CACHE INTERNAL "wxWidgets installation dir")
set (ENV_WX_CONFIG ${STAGED_INSTALL_PREFIX}/bin/wx-config)
file (TO_NATIVE_PATH "${ENV_WX_CONFIG}" ENV_WX_CONFIG)
set(ENV_WX_CONFIG ${ENV_WX_CONFIG} CACHE INTERNAL "wx-config dir")
endif()
The idea here is to check if wxWidgets is already installed in the system:
find_package(wxWidgets QUIET)
This code runs the FindwxWidgets.cmake script which comes built-in with the CMake installation. Note the QUIET parameter: we don’t require wxWidgets at this point, we just want to check if it’s installed and take necessary steps based on the result.
If the library is already in the system, we define a dummy library in order to satisfy the main project’s dependencies (remember: wx_cmake_template_core depends on wxWidgets_external - we’ll see the definitions for that later on).
If it isn’t, then we define wxWidgets_external as an ExternalProject, pointing its source to the github URL and forwarding CMake’s libraries, so that they can be used by the wxWidgets build process (the wxWidgets library also uses CMake for building). Note the ${STAGED_INSTALL_PREFIX}
variable - it points to the build/stage directory which will be created during the build process and will contain the installation products of the downloaded wxWidgets library.
wx_cmake_template_core
Also note the wxWidgets_ROOT_DIR and ENV_WX_CONFIG variables set at the end of the last script. This is what makes our idea work, because in src/CMakeLists.txt, we once again look for wxWidgets using find_package:
# ... (see github for full source)
# ...
set(wxWidgets_USE_STATIC 1)
find_package(wxWidgets REQUIRED)
set(SRCS main.cpp)
include(${wxWidgets_USE_FILE})
if (APPLE)
add_executable(main MACOSX_BUNDLE ${SRCS} )
else()
add_executable(main WIN32 ${SRCS}) # also works for Linux
endif()
target_link_libraries(main PRIVATE ${wxWidgets_LIBRARIES})
Because the thirdparty/wxwidgets/CMakeLists.txt has to be parsed before src/CMakeLists.txt (this order is set up in the root CMakeLists.txt), we know we already checked for wxWidgets in the previous script. If that check found wxWidgets, it means that src/CMakeLists.txt will also find it. If the previous check failed, then we set up the wxWidgets_ROOT_DIR
and ENV_WX_CONFIG
variables which will be used by the find_package
in our src script to look for our temporary wxWidgets installation that we downloaded from GitHub.
So src/CMakeLists.txt does not really care which wxWidgets will be used for linking (preinstalled on the system, or downloaded from github). The thirdparty/wxwidgets/CMakeLists.txt does all the dirty work of selecting that and setting the correct variables. The main build script just calls find_package which selects the correct wxWidgets binaries.
Note: the ENV_WX_CONFIG variable needs to be set only because of a bug in CMake’s findwxWidgets script. We should only have to set the wxWidgets_ROOT_DIR. Watch the following YouTube vid for a more thorough explanation: Single Codebase UI Apps in C++ for Linux, Windows and MacOS - building with wxWidgets and CMake
Root CMakeLists.txt
Let’s see how this all is glued together with the root CMakeLists.txt script:
cmake_minimum_required(VERSION 3.6 FATAL_ERROR)
project(wx_cmake_template LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# ExternalProject base
set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects)
set(STAGED_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/stage)
add_subdirectory(thirdparty)
include(ExternalProject)
ExternalProject_Add(${PROJECT_NAME}_core
DEPENDS
wxWidgets_external
SOURCE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/src
CMAKE_ARGS
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}
-DwxWidgets_ROOT_DIR=${wxWidgets_ROOT_DIR}
-DENV_WX_CONFIG=${ENV_WX_CONFIG}
CMAKE_CACHE_ARGS
-DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
-DCMAKE_PREFIX_PATH:PATH=${CMAKE_PREFIX_PATH}
BUILD_ALWAYS
1
INSTALL_COMMAND
""
)
Note the order in which we declare/include the components. First, we add the thirdparty directory to parse the script there:
add_subdirectory(thirdparty)
There we define the wxWidgets_external
component: we check for wxWidgets’ existence in the system and set wxWidgets_external
to be either a dummy library or a full ExternalProject set to download wxWidgets from github. In the second case, we also set the wxWidgets_ROOT_DIR
variable to prepare the environment for wx_cmake_template_core.
The wx_cmake_template_core ExternalProject depends on wxWidgets_external, so we’re sure the wxWidget library will be downloaded and compiled (if needed) before we build the src directory. We also pass wxWidgets_ROOT_DIR
so that wx_cmake_template_core searches for correct library binaries.
Build process
Before building the project, we need to make sure the build tools (compiler, etc.) and CMake are installed on the system. For detailed instructions on how to do this, check out the following video: Single Codebase UI Apps in C++ for Linux, Windows and MacOS - building with wxWidgets and CMake. In short you’ll need to:
- install
libgtk-3-dev
andcmake
for Linux, - install homebrew and then
brew cmake
to install CMake and build tools (as a CMake dependency) for Mac, - install VisualStudio (the free Community Edition will work), selecting C++ tools in the installation menu in Windows.
After installing the prerequisites and cloning the sample source from GitHub, you can build the code with the same commands on each of the supported operating systems. In order to configure CMake build, run:
cmake -S. -Bbuild
This creates a local build directory in the project directory tree, where the build artifacts will be kept. Then run:
cmake --build build
This runs the build process. If the script does not find wxWidgets, the process will download and build it, which may take some time. Since we’re installing wxWidgets in a local directory, subsequent builds won’t trigger wxWidgets recompiling, and will therefore run much faster.
The build/subprojects/Build directory will contain build artifacts in the correct format (ELF for Linux, EXE for Windows, MacOS bundle for Mac). You may now run the compiled project and enjoy a native-looking interface on each one of the platforms.

Native look and feel for each platform
Conclusion
We’ve demonstrated how to use CMake’s ExternalProject feature to streamline project building and dependency management, regardless of the platform we’re using. While CMake may be difficult to learn, it’s a powerful build system which can make multiplatform builds and automatic dependency download easy to manage.
Remember to check out the full source code of the project: https://github.com/lszl84/wx_cmake_template and YouTube video with a detailed explanation of the whole process, from code walktrough to detailed build demonstration for each platform:
Single Codebase UI Apps in C++ for Linux, Windows and MacOS — building with wxWidgets and CMake