Managing Multi-Compiler CMake Projects in VS Code

Learn how to build host utilities alongside embedded firmware in CMake by using ExternalProject. This guide covers setting up multiple compilers, integrating utility projects, and configuring VS Code workspaces for smooth IntelliSense.

Andre Leppik

A while back, I started working on a project that wasn’t just firmware, but also included a few utility programs. Everything lived in one repo, but needed different compilers, arm-none-eabi-gcc for the firmware and the regular gcc for the utilities.

At first, I just opened the project in VS Code, but IntelliSense got completely confused. Switching between parts of the project would constantly break code completion, error squiggles popped up everywhere, and the editor didn’t know which compiler it should assume.

That’s when I decided to update the setup to play nicely with multiple toolchains in one project.

Creating a Subproject

By default, a CMake project configuration is tied to a single compiler toolchain, but our project needs two different compilers. In our case, that's where CMake ExternalProject module comes in handy.

Suppose we have a project with the following structure:

  • /src directory contains the embedded firmware source files. Which will be built using the arm-none-eabi toolchain.
  • /utils directory contains a small host-side tool that will be built with gcc.

To use both of the compilers, we need to treat the utils program as a separate project and integrate it into the main build using CMake’s ExternalProject module. In that way, our targets can have separate configurations but act as a single project.

First, ensure the utility program has its own CMakeLists.txt file in the /utils directory. Here’s an example configuration:

cmake_minimum_required(VERSION 3.16)
project(UtilProgram LANGUAGES C CXX)

# Specify compiler (optional if using the default system compiler)
set(CMAKE_C_COMPILER gcc)
set(CMAKE_CXX_COMPILER g++)

# Define the utility target
add_executable(util_program main.cpp)

# Add include directories and libraries if needed
target_include_directories(util_program PRIVATE ${CMAKE_SOURCE_DIR}/include)

It's a simple CMake file that uses gcc to build the utility program executable consisting of just one file. Next, we will need to integrate into the root build configuration for it to appear as a target.

Integrating into the Root Build

To include the /utils subproject in the main build configuration, we will use CMake's ExternalProject module. Start by including the module in the root build configuration.

include(ExternalProject)

Finally, we can add the project definition:

ExternalProject_Add(
    util_project
    SOURCE_DIR ${CMAKE_SOURCE_DIR}/utils
    BINARY_DIR ${CMAKE_BINARY_DIR}/utils
    CMAKE_ARGS
        -DCMAKE_BUILD_TYPE=Release
    BUILD_ALWAYS ON
    INSTALL_COMMAND 
        ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/utils
)

Now the utility subproject appears as a separate target in our build system.

VS Code Target Selection with External Project

Configuring VS Code

Although, we managed to create a multi-compiler project, VS Code InteliSense may struggle as the external project sources aren't part of the main project's configuration. To fix this issue we'll use VS Code Workspaces.

Step 1: Exclude the Utility Directory

Start by opening the whole project in VS Code and add the following lines to .vscode/settings.json to exclude the /utils directory from the main view.

"files.exclude": {
    "**/utils": true
}

Excluding the directory from the project will signal InteliSense to not parse or handle any of the files in that directory.

Step 2: Create a VS Code Workspace

A Visual Studio Code workspace is the collection of one or more folders that are opened in a VS Code window (instance). In most cases, a single folder is the workspace, but we need to have a workspace with multiple separate folders. To create a multi folder workspace do the following steps:

  • Open VS Code and go to File → Add Folder to Workspace....
  • Select the /utils directory.
  • Save the workspace in the project root directory: File → Save Workspace As....

Once everything is done, the CMake Extension view looks something like that. From here it possible to activate CMake workspace to build or browse the content.

VS Code Workspace View in CMake Extension

What’s Next?

Now that we’ve set up a multi-compiler CMake project in VS Code, we could explore setting up debugging for both of the projects. We could use the launch.json file to define separate debug configurations for each target.

Interested more about CMake? Check out our other post: "Including External Libraries in CMake Projects".

Need help with embedded systems development?

Whether you're building something new, fixing stability issues, or automating what slows your team down — we can help.