43

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
  • 43,322
  • 6
  • 62
  • 109
Blaze
  • 641
  • 2
  • 6
  • 12
  • 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

8 Answers8

27

CMake currently does not support C++20 modules.

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.

Update: CMake 3.20 introduces experimental support for Modules with the Ninja Generator (and only for Ninja). Details can be found in the respective pull request. At this stage, this feature is still highly experimental and not intended for production use. If you intend to play around with this anyway, you really should be reading both the Fortran modules paper and the dependency format paper to understand what you're getting into.

ComicSansMS
  • 46,689
  • 13
  • 143
  • 152
  • 6
    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
23

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,073
  • 1
  • 9
  • 22
  • 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 – Pyzyryab 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
9

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,124
  • 20
  • 35
  • 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
  • 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
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.

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
  • 50,375
  • 11
  • 61
  • 80
sbh
  • 349
  • 2
  • 13
  • 12
    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 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
  • 963
  • 1
  • 10
  • 25
0

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
  • 121
  • 5
  • 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! – Pyzyryab 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 – Pyzyryab Jan 09 '22 at 17:28
-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
  • 167
  • 1
  • 4