Fisher Sun šŸŽ£

C++20 Modules with CLion and CMake

cxx

Iā€™ve been working through Crafting Interpreters in C++20, so I tried out using the new modules. There were a couple of unintuitive things with using modules with CLion and CMake, so Iā€™ll document them here so that it may help future me (and maybe other people too).

Skip to finished CMakeLists.txt

Creating a module

Create a CLion C++ Executable project with language standard C++ 20.

Create a new module by selecting New -> C++ Module Interface Unit. Give it a name, and leave ā€œAdd to targetsā€ checked.

Then, fill out your module:

// carrot.ixx
export module carrot;
export class Carrot {
public:
static constexpr int VITAMIN_D = 10'191;
};

Import your module from main.cpp and use it:

// main.cpp
#include <iostream>
import carrot;
int main() {
std::cout << Carrot::VITAMIN_D << std::endl;
return 0;
}

When you run the executable, youā€™ll see that the build errors! Youā€™ll get a message like:

CMake Error: Output CMakeFiles/demo.dir/carrot.ixx.o provides the `carrot` module but it is not found in a `FILE_SET` of type `CXX_MODULES`

Even though we had CLion add our file to the CMake build target, CMake isnā€™t satisfied. Because we created a module interface unit (a file that exports a module), weā€™ll need to add it to a special file set.

A modules file set

Here is what our projectā€™s CMakeLists.txt has by default:

cmake_minimum_required(VERSION 3.30)
project(demo)
set(CMAKE_CXX_STANDARD 20)
add_executable(demo main.cpp
carrot.ixx)

Underneath that, add a file set of type CXX_MODULES and add the module interface unit (carrot.ixx) to it:

target_sources(demo
PUBLIC
FILE_SET modules TYPE CXX_MODULES FILES
carrot.ixx)

Run the executable again, and the build should succeed.

Creating a file glob for module interface units

This configuration allowed us to add one module interface unit to our build, but each time we create a new one, weā€™ll need to manually add it, which would get annoying.

To resolve this, we can create a file glob for module interface units:

file(GLOB_RECURSE MODULE_INTERFACE_UNITS "*.ixx")

With this, we can replace the hard-coded list of files with the file glob:

target_sources(demo
PUBLIC
FILE_SET modules TYPE CXX_MODULES FILES
${MODULE_INTERFACE_UNITS})

Finished CMakeLists.txt

Here is what my finished CMakeLists.txt looks like (Iā€™ve added an apple.ixx module to verify that it works):

cmake_minimum_required(VERSION 3.30)
project(demo)
set(CMAKE_CXX_STANDARD 20)
add_executable(demo main.cpp
carrot.ixx
apple.ixx)
file(GLOB_RECURSE MODULE_INTERFACE_UNITS "*.ixx")
target_sources(demo
PUBLIC
FILE_SET modules TYPE CXX_MODULES FILES
${MODULE_INTERFACE_UNITS})