60

Clang and MSVC already supports Modules TS from unfinished C++20 standard. Can I build my modules based project with CMake or other build system and how?

I tried build2, it supports modules and it works very well, but i have a question about it's dependency management (UPD: question is closed).

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Blaze
  • 813
  • 2
  • 8
  • 13
  • You can likely use a combination of setting [`CXX_STANDARD`](https://cmake.org/cmake/help/latest/prop_tgt/CXX_STANDARD.html), and a CMake if-statement to check for Clang or MSVC, then adding the appropriate compiler flags based on your compiler. – Kevin Aug 01 '19 at 11:50
  • I tried to compile using Clang with `-std=c++2a -fmodules-ts` and it says **fatal error: module 'VulkanRender' not found**. How can i tell clang where my modules are stored? – Blaze Aug 01 '19 at 19:18
  • 1
    I tried build2 again and I answered my question. Everything works excellent! – Blaze Aug 02 '19 at 05:02
  • I have the CMake project example with C++ modules. Works with GCC. https://github.com/bravikov/cmake-cpp-modules-example – Dmitry Bravikov Jan 03 '22 at 19:44
  • You can also try xmake, it supports modules and headerunits too. https://github.com/xmake-io/xmake/tree/master/tests/projects/c%2B%2B/modules – ruki Nov 09 '22 at 08:47

9 Answers9

42

Experimental Modules Support

This reflects the status as of CMake 3.27.

I will keep updating this answer as the situation changes.

Important: CMake's support for C++20 modules is currently functional, but still experimental. Things may work in some cases but break in others. Expect bugs and breaking changes between versions! See also the relevant issue in the CMake issue tracker.

Note that supporting modules requires far more support from the build system than inserting a new compiler option. It fundamentally changes how dependencies between source files have to be handled during the build: In a pre-modules world all cpp source files can be built independently in any order. With modules that is no longer true, which has implications not only for CMake itself, but also for the downstream build system.

Take a look at the CMake Fortran modules paper for the gory details. From a build system's point of view, Fortran's modules behave very similar to the C++20 modules.

Prerequisites

Proper integration currently only works with the following generators:

  • Ninja version 1.10 or newer
  • Visual Studio 2022 version 19.34 or newer.

Module dependency scanning is currently supported by the following compilers:

  • MSVC compiler version 19.34 or newer
  • LLVM/Clang version 16 or newer.

Be sure that both your compiler and build system are sufficiently up-to-date!

Note: Getting Clang to work requires at least Clang version 16 and additionally may at this point still require some fiddling with CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE. Since this is way too deep into the tool internals, I will not cover that in this answer! Check out CMake's feature guide if you want to try this out. This should get resolved in a future version of CMake though.

Some General Remarks

  • Always use the absolute latest CMake, build tool, and compiler versions that you can. This feature is still under heavy development and receives a constant stream of vital bugfixes.
  • Read the docs:
    • D1483 explains how the build process looks like for modules and why it's considerably more difficult than a non-modules build. This is an essential read.
    • CMake Experimental Features Guide Documents the limitations of the current experimental implementation in CMake. If something doesn't work as expected, check here first.
    • Familiarize yourself with the basic feature set and vocabulary of Modules. Daniela Engert's talk is an excellent introduction.
    • Read Kitware's blog post for additional info not covered in this answer, like how to try out Modules with a custom build of gcc.
  • The tooling will produce a bunch of .json files during the build, which contain the data used by the build system to track module dependencies. If something doesn't work as expected, these can be very useful for debugging.
  • None of this is cleared for use in production at this point! Keep in mind that this is an experimental feature that is mainly being made available so that compiler and tool implementers can iron out the bugs.

Activating Modules Support in CMake

Since CMake's support for modules is experimental at this point, you will have to opt-in to the feature before being able to use it:

cmake_minimum_required(VERSION 3.27)

project(my_modules_project)

set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API aa1f7df0-828a-4fcd-9afc-2dc80491aca7)
set(CMAKE_CXX_STANDARD 20)

The last line here is optional, but your compiler may refuse to compile code using modules if you don't request C++20 support. Note that C++20 does not contain a modularized version of the standard library, you will need at least C++23 for that.

Setting CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API activates the modules support. The magic number for CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API changes with every CMake release, so be sure to double-check the docs for your CMake release if CMake complains about not recognizing the module-related commands.

Using Modules

Module source files need to be specified using the FILE_SET feature of CMake's target_sources command. FILE_SET is quite useful for libraries without modules as well, so check it out if you don't know the feature yet.

Module source files are distinguished from normal source files by being part of the special CXX_MODULES file set:

add_executable(my_app)
target_sources(my_app PRIVATE
  FILE_SET all_my_modules TYPE CXX_MODULES
  BASE_DIRS
    ${PROJECT_SOURCE_DIR}
  FILES
    a.cppm
    b.cppm
)
target_sources(my_app PRIVATE
  main.cpp
)

Here a.cppm and b.cppm are module source files that can make use of the export keyword of C++20 modules. In contrast main.cpp may use the import keyword, but not the export keyword. The distinguishing factor here is the FILE_SET, not the file extension! We simply use .cppm for module sources here for illustrative purposes.

Note that if your source file is a module implementation unit it must not be part of the CXX_MODULES fileset! You should also not use a module-style file extension like .cppm or .ixx, but instead use plain .cpp as the file extension for these, as some compilers may otherwise treat the files as module interface units, which will break your build.

Header units are currently not supported anywhere (neither by CMake nor by any of the major build systems) and there are serious concerns about the implementability of this feature. Daniel Ruoso gave an excellent talk at C++Now 2023 (video) explaining those concerns. You should stick with named modules for now.

A complete working example

You can also find this example on Github.

// a.cppm
module;

#include <iostream>

export module MyModule;

int hidden() {
    return 42;
}

export void printMessage() {
    std::cout << "The hidden value is " << hidden() << "\n";
}
// main.cpp
import MyModule;

int main() {
    printMessage();
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.27)

set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API aa1f7df0-828a-4fcd-9afc-2dc80491aca7)

project(modules-example)

set(CMAKE_CXX_STANDARD 20)

add_executable(demo)
target_sources(demo
    PUBLIC
    main.cpp
)
target_sources(demo
  PUBLIC
    FILE_SET all_my_modules TYPE CXX_MODULES FILES
    a.cppm
)

If you set everything up correctly, CMake should give you the following warning during its configure stage:

CMake Warning (dev) at CMakeLists.txt:??? (target_sources):
  CMake's C++ module support is experimental.  It is meant only for
  experimentation and feedback to CMake developers.
This warning is for project developers.  Use -Wno-dev to suppress it.

The project should still build fine and rebuild correctly when changing the source files.

If you get an error saying

target_sources File set TYPE may only be "HEADERS"

it means that either your CMake version is too old, or you did not set up the Modules support correctly. Double-check CMake's documentation in this case.

ComicSansMS
  • 51,484
  • 14
  • 155
  • 166
  • 7
    Did anything change in the last 18 month? – Lothar Sep 27 '20 at 10:51
  • @Lothar MSVC is now shipping a much more capable Modules implementation than before on version 16.8 and higher. However, this version is currently still in preview and not available on the main release channel. It is also unclear at the moment, whether that implementation already provides everything that would be needed for a proper build system integration with CMake. In general, due to the deep impact of the feature for the overall build process, I expect it will take a couple of more years (!) before this issue is resolved to a level where you can use it for a productive code base. – ComicSansMS Sep 27 '20 at 13:36
  • 4
    So now 3 and a half years later there is still no support for modules and everything is still experimental (based on https://gitlab.kitware.com/cmake/cmake/-/issues/18355) . So sad. – Lothar Jul 19 '22 at 01:12
  • The CppCon channel just released [a talk of one of the Kitware founders](https://youtu.be/5X803cXe02Y) sharing an update on C++20 modules support. Basically: it's coming. – adentinger Nov 02 '22 at 21:07
  • Does the last part - "Original answer" - have any value except historical one? If no, then it could be safely removed: the edit history already incorporates that info. – Tsyvarev May 15 '23 at 15:05
  • But your first part is titled "Experimental Modules Support" and you intend to update it regularly. It looks like the better place for the issue link and explanations why modules support is currently experimental. – Tsyvarev May 15 '23 at 17:33
  • 1
    Note that as of 1st June 2023, the `CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP` flag is not necessary anymore and got removed from CMake [here](https://github.com/Kitware/CMake/commit/0183e1bca379199949210217869c3a214702eba1). – Splines Jun 02 '23 at 15:51
  • (Note that at least that's the date the commit was pushed, don't know in which CMake version this will be reflected). – Splines Jun 02 '23 at 15:59
  • With Ninja 1.10.0, I get an error suggesting that "Ninja 1.11 or higher is required" ("The Ninja generator does not support C++20 modules using Ninja version 1.10.0 due to lack of required features.") – Splines Jun 02 '23 at 16:16
26

This works on Linux Manjaro (same as Arch), but should work on any Unix OS. Of course, you need to build with new clang (tested with clang-10).

helloworld.cpp:

export module helloworld;
import <cstdio>;
export void hello() { puts("Hello world!"); }

main.cpp:

import helloworld;  // import declaration

int main() {
    hello();
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(main)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(PREBUILT_MODULE_PATH ${CMAKE_BINARY_DIR}/modules)

function(add_module name)
    file(MAKE_DIRECTORY ${PREBUILT_MODULE_PATH})
    add_custom_target(${name}.pcm
            COMMAND
                ${CMAKE_CXX_COMPILER}
                -std=c++20
                -stdlib=libc++
                -fmodules
                -c
                ${CMAKE_CURRENT_SOURCE_DIR}/${ARGN}
                -Xclang -emit-module-interface
                -o ${PREBUILT_MODULE_PATH}/${name}.pcm

            )
endfunction()


add_compile_options(-fmodules)
add_compile_options(-stdlib=libc++)
add_compile_options(-fbuiltin-module-map)
add_compile_options(-fimplicit-module-maps)
add_compile_options(-fprebuilt-module-path=${PREBUILT_MODULE_PATH})

add_module(helloworld helloworld.cpp)
add_executable(main
        main.cpp
        helloworld.cpp
        )
add_dependencies(main helloworld.pcm)

warchantua
  • 1,154
  • 1
  • 10
  • 24
  • Why do you need to add `helloworld.cpp` to sources of the `main` executable? – Luka Govedič Feb 19 '21 at 17:27
  • You need to create object file (.o) for that cpp, and link together with other object files. – warchantua Feb 19 '21 at 19:56
  • with CMake you could probably add another function, which will create OBJECT library from module cpp file(s), and add them to executable, but that's out of scope of this answer. – warchantua Feb 19 '21 at 20:06
  • Tried on Manjaro with Clang 13 and this is the result: `C++20 ‘import’ only available with ‘-fmodules-ts’, which is not yet enabled with ‘-std=c++20’` Also, -stdlibm -fmodules and -Xclang are unrecognized compiler options – Alex Vergara Jan 09 '22 at 17:16
  • For anyone curious about it working with JetBrains' Clion, the code compiles and runs using the above recipe on my M1 Mac (using homebrew llvm), but Clion can't make sense of the modules and it lints everywhere. It's a known issue though. – marital_weeping Jan 19 '22 at 14:50
  • How to split module interface and implementation? @warchantua – ideal Apr 30 '22 at 10:44
14

Assuming that you're using gcc 11 with a Makefile generator, the following code should work even without CMake support for C++20:

cmake_minimum_required(VERSION 3.19) # Lower versions should also be supported
project(cpp20-modules)

# Add target to build iostream module
add_custom_target(std_modules ALL
    COMMAND ${CMAKE_COMMAND} -E echo "Building standard library modules"
    COMMAND g++ -fmodules-ts -std=c++20 -c -x c++-system-header iostream
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

# Function to set up modules in GCC
function (prepare_for_module TGT)
    target_compile_options(${TGT} PUBLIC -fmodules-ts)
    set_property(TARGET ${TGT} PROPERTY CXX_STANDARD 20)
    set_property(TARGET ${TGT} PROPERTY CXX_EXTENSIONS OFF)
    add_dependencies(${TGT} std_modules)
endfunction()

# Program name and sources
set (TARGET prog)
set (SOURCES main.cpp)
set (MODULES mymod.cpp)

# Setup program modules object library
set (MODULE_TARGET prog-modules)
add_library(${MODULE_TARGET} OBJECT ${MODULES})
prepare_for_module(${MODULE_TARGET})

# Setup executable
add_executable(${TARGET} ${SOURCES})
prepare_for_module(${TARGET})

# Add modules to application using object library
target_link_libraries(${TARGET} PRIVATE ${MODULE_TARGET})

Some explanation:

  1. A custom target is added to build the standard library modules, in case you want to include standard library header units (search for "Standard Library Header Units" here). For simplicity, I just added iostream here.
  2. Next, a function is added to conveniently enable C++20 and Modules TS for targets
  3. We first create an object library to build the user modules
  4. Finally, we create our executable and link it to the object library created in the previous step.

Not consider the following main.cpp:

import mymod;

int main() {
    helloModule();
}

and mymod.cpp:

module;
export module mymod;

import <iostream>;

export void helloModule() {
    std::cout << "Hello module!\n";
}

Using the above CMakeLists.txt, your example should compile fine (successfully tested in Ubuntu WSL with gcc 1.11.0).

Update: Sometimes when changing the CMakeLists.txt and recompiling, you may encounter an error

error: import "/usr/include/c++/11/iostream" has CRC mismatch

Probably the reason is that every new module will attempt to build the standard library modules, but I'm not sure. Unfortunately I didn't find a proper solution to this (avoiding rebuild if the gcm.cache directory already exists is bad if you want to add new standard modules, and doing it per-module is a maintenance nightmare). My Q&D solution is to delete ${CMAKE_BINARY_DIR}/gcm.cache and rebuild the modules. I'm happy for better suggestions though.

andreee
  • 4,459
  • 22
  • 42
  • Worth to add: it works with makefiles generator, and does NOT work with nonja - error "inputs may not also have inputs" shows. – Mariusz Jaskółka Sep 01 '21 at 06:53
  • 1
    About the header units & CRC mismatch problem: You can make a large header unit with `-fmodule-header` (call it "std.h" or something) that includes all the standard library and import it instead of the regular headers. – unddoch Jan 16 '22 at 13:59
  • Perfect! Thank you. Worked flawlessly on Ubuntu 22 with GCC 12.1 – ervinbosenbacher Jul 12 '22 at 10:16
4

CMake ships with experimental support for C++20 modules: https://gitlab.kitware.com/cmake/cmake/-/blob/master/Help/dev/experimental.rst

This is tracked in this issue: https://gitlab.kitware.com/cmake/cmake/-/issues/18355

There is also a CMakeCXXModules repository that adds support for modules to CMake.

https://github.com/NTSFka/CMakeCxxModules

Amin Ya
  • 1,515
  • 1
  • 19
  • 30
2

While waiting for proper C++20 modules support in CMake, I've found that if using MSVC Windows, for right now you can make-believe it's there by hacking around the build instead of around CMakeLists.txt: continously generate with latest VS generator, and open/build the .sln with VS2020. The IFC dependency chain gets taken care of automatically (import <iostream>; just works). Haven't tried Windows clang or cross-compiling. It's not ideal but for now at least another decently workable alternative today, so far.

Important afterthought: use .cppm and .ixx extensions.

2

With C++20 Modules the file compilation order matters, which is totally new. That's why the implementation is complicated and still experimental in 2023. Please read the authors blogpost

Mariusz Jaskółka
  • 4,137
  • 2
  • 21
  • 47
1

I was not able to find Cmake support for modules. Here is an example how to use modules using clang. I am using Mac and this example works ok on my system. It took me quite a while to figure this out so unsure how general this is across linux or Windows.

Source code in file driver.cxx

import hello;
int main() { say_hello("Modules"); } 

Source code in file hello.cxx

#include <iostream>
module hello;
void say_hello(const char *n) {
  std::cout << "Hello, " << n << "!" << std::endl;
}

Source code in file hello.mxx

export module hello;
export void say_hello (const char* name);

And to compile the code with above source files, here are command lines on terminal

clang++ \
  -std=c++2a                        \
  -fmodules-ts                      \
  --precompile                      \
  -x c++-module                     \
  -Xclang -fmodules-embed-all-files \
  -Xclang -fmodules-codegen         \
  -Xclang -fmodules-debuginfo       \
  -o hello.pcm hello.mxx

clang++ -std=c++2a -fmodules-ts -o hello.pcm.o -c hello.pcm

clang++ -std=c++2a -fmodules-ts -x c++ -o hello.o \
  -fmodule-file=hello.pcm -c hello.cxx

clang++ -std=c++2a -fmodules-ts -x c++ -o driver.o \
  -fmodule-file=hello=hello.pcm -c driver.cxx

clang++ -o hello hello.pcm.o driver.o hello.o

and to get clean start on next compile

rm -f *.o
rm -f hello
rm -f hello.pcm

expected output

./hello
Hello, Modules!

Hope this helps, all the best.

Benjamin Bannier
  • 55,163
  • 11
  • 60
  • 80
sbh
  • 407
  • 4
  • 13
  • 16
    This is a nice answer - but not for this question. You might consider formulating a question ("Simple example of building a C++ program using a module?") modules and posting it with this answer. – einpoklum Apr 16 '20 at 07:03
1

CMake does not currently support C++20 modules like the others have stated. However, module support for Fortran is very similar, and perhaps this could be easily changed to support modules in C++20.

http://fortranwiki.org/fortran/show/Build+tools

Now, perhaps there i an easy way to modify this to support C++20 directly. Not sure. It is worth exploring and doing a pull request should you resolve it.

Lord Alveric
  • 327
  • 2
  • 4
1

Add MSVC version (revised from @warchantua 's answer):

cmake_minimum_required(VERSION 3.16)

project(Cpp20)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(PREBUILT_MODULE_DIR ${CMAKE_BINARY_DIR}/modules)
set(STD_MODULES_DIR "D:/MSVC/VC/Tools/MSVC/14.29.30133/ifc/x64") # macro "$(VC_IFCPath)" in MSVC

function(add_module name)
    file(MAKE_DIRECTORY ${PREBUILT_MODULE_DIR})
    add_custom_target(${name}.ifc
            COMMAND
                ${CMAKE_CXX_COMPILER}
                /std:c++latest
                /stdIfcDir ${STD_MODULES_DIR}
                /experimental:module
                /c
                /EHsc
                /MD
                ${CMAKE_CURRENT_SOURCE_DIR}/${ARGN}
                /module:export
                /ifcOutput
                ${PREBUILT_MODULE_DIR}/${name}.ifc
                /Fo${PREBUILT_MODULE_DIR}/${name}.obj
            )
endfunction()

set(CUSTOM_MODULES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/modules)
add_module(my_module ${CUSTOM_MODULES_DIR}/my_module.ixx)

add_executable(test
    test.cpp
    )
target_compile_options(test
    BEFORE
    PRIVATE
    /std:c++latest
    /experimental:module
    /stdIfcDir ${STD_MODULES_DIR}
    /ifcSearchDir ${PREBUILT_MODULE_DIR}
    /reference my_module=${PREBUILT_MODULE_DIR}/my_module.ifc
    /EHsc
    /MD
)
target_link_libraries(test ${PREBUILT_MODULE_DIR}/my_module.obj)
add_dependencies(test my_module.ifc)
Harold
  • 170
  • 7
  • Can you please explain how you set up your files in your directory? I am following your CMakeLists and getting weirdo MSVC 2022 compiler errors, like not founding de `STD_MODULES_DIR` and use of `/module:export` requires `/experimental:module` which is already on the CMake file! – Alex Vergara Jan 07 '22 at 10:19
  • The `STD_MODULES_DIR` is required only if you use the standard library modules in your project (e.g.`std.core`), if you use the `#include` directives, no need use add this dir and `stdIfcDir` compile option. – Harold Jan 08 '22 at 07:37
  • Maybe you'd better try with modules in command line first ([this is a good tutorial](https://devblogs.microsoft.com/cppblog/using-cpp-modules-in-msvc-from-the-command-line-part-1/)), my demo CMakeLists is just wrapped command line cmds into cmake. – Harold Jan 08 '22 at 07:43
  • I deleted /stdIfcDir and /ifcSearchDir and works perfectly. Also I can use import `std.core` or whatever without those compile options, which aren't working for me. I am using the 2022 MSVC compiler, but as I said, now I can fully compile my project – Alex Vergara Jan 09 '22 at 17:28