Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use c++20 modules with CMake?

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).

like image 934
Blaze Avatar asked Aug 01 '19 00:08

Blaze


People also ask

Does CMake support C++ modules?

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.

Where do I put CMake modules?

Using Modules For CMake, these sections are called cmake-modules and can be found in the Modules subdirectory of your installation.

Can you use CMake with C?

In the C/C++ ecosystem, the best tool for project configuration is CMake. CMake allows you to specify the build of a project, in files named CMakeLists. txt, with a simple syntax (much simpler than writing Makefiles).

How do I import a module in C++?

You can use modules side by side with header files. A C++ source file can import modules and also #include header files. In some cases, you can import a header file as a module rather than include it textually by using #include in the preprocessor.


4 Answers

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.

like image 108
ComicSansMS Avatar answered Oct 22 '22 08:10

ComicSansMS


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)

like image 43
warchantua Avatar answered Oct 22 '22 09:10

warchantua


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.

like image 11
andreee Avatar answered Oct 22 '22 09:10

andreee


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.

like image 2
babluychunks Avatar answered Oct 22 '22 10:10

babluychunks