import CMake; the Experiment is Over!

October 18, 2023

At last the experiment is over, and CMake 3.28 has official support for C++ 20 named modules enabled without having to set the CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API variable. This has been a long adventure involving the coordination of the SG15 tooling group, and developers of three major compilers: Visual Studio, LLVM/Clang, and GCC. To read about the entire process and the technical details of how CMake supports named modules see this blog: https://www.kitware.com/import-cmake-c20-modules/.

Versions

C++ 20 named modules are now supported in CMake 3.28 and newer when using  Ninja and Visual Studio Generators (VS 2022 and newer in combination with the MSVC 14.34 toolset, provided with VS 17.4, and newer), LLVM/Clang 16.0 and newer, and GCC 14 (after the 2023-09-20 daily bump) and newer.

Why now?

Once support for p1689 was merged into GCC , three major compilers now have the support required to build named C++ 20 module files in the correct order. This was key to the feature exiting its experimental status in CMake. MSVC was the first to implement p1689 support, and the CMake team is grateful to Microsoft for providing us with an actual compiler to test against. The next to follow was LLVM/Clang, a big shout out to Chuanqi Xu for getting this implemented and upstreamed in Clang. And finally thanks to Ben Boeckel and his work with the GCC team in getting support into GCC 14. 

We would also like to thank Bret Brown and Daniel Ruoso and Bloomberg Engineering for both their technical and financial support that made this work happen.

There  is still plenty of work to do, including but not limited to support of 'import std' and in-project flag differences. However, this is a big first step towards making C++ 20 modules a reality in the C++ community.

Kick the tires

If you want to try named modules from C++ 20, now is the time! The first thing you are going to need is a compiler that supports C++ 20 modules and p1689.  The next thing you will need is a build of CMake 3.28 or newer.

Getting a compiler

For Visual Studio, install VS 2022 or newer, in combination with the MSVC 14.34 toolset provided with VS 17.4, or newer.

For Clang, install LLVM/Clang 16.0 or newer.

For GCC, install GCC 14 and GCC 14 2023-09-20 daily bump or newer.

Getting CMake

There are a variety of ways to get CMake including the package manager for your system. However, for modules to work it must be 3.28 or newer. If you want to get CMake from the official web site you can choose one of the following:

Hello world

As an example, here is a very simple hello world program that uses modules. The program has a file foo.cxx which implements a module called foo, a file main.cxx which imports foo and uses the module to print hello world, and a CMakeLists.txt file that contains the CMake code to build this simple example.

// Global module fragment where #includes can happen
module;
#include <iostream>

// first thing after the Global module fragment must be a module command
export module foo;

export class foo {
public:
  foo();
  ~foo();
  void helloworld();
};

foo::foo() = default;
foo::~foo() = default;
void foo::helloworld() { std::cout << "hello world\n"; }
import foo;

int main()
{
  foo f;
  f.helloworld();
  return 0;
}
cmake_minimum_required(VERSION 3.28)
project(std_module_example CXX)

# Turning off extensions avoids an issue with the clang 16 compiler
# clang 17 and greater can avoid this setting
set(CMAKE_CXX_EXTENSIONS OFF)
# Set the version of C++ for the project
set(CMAKE_CXX_STANDARD 20)
# Create a library
add_library(foo)
# Add the module file to the library
target_sources(foo
  PUBLIC
    FILE_SET CXX_MODULES FILES
      foo.cxx
)
# Create an executable
add_executable(hello main.cxx)
# Link to the library foo
target_link_libraries(hello foo)

Building and running

To test, this I built llvm16 on my Macbook M2 laptop. I used the Ninja generator with CMake to create the build files, ran ninja to perform the build and finally ran the simple example.

 

% CXX=clang++ CC=clang cmake -GNinja ..

-- The CXX compiler identification is Clang 16.0.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Users/hoffman/Work/modules/llvm16-inst/bin/clang++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.5s)
-- Generating done (0.0s)
-- Build files have been written to: /Users/hoffman/Work/modules/blog/b

% ninja -v
[1/8] "/Users/hoffman/Work/modules/llvm16-inst/bin/clang-scan-deps" -format=p1689 -- /Users/hoffman/Work/modules/llvm16-inst/bin/clang++   -std=c++20 -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX13.3.sdk -x c++ /Users/hoffman/Work/modules/blog/main.cxx -c -o CMakeFiles/hello.dir/main.cxx.o -MT CMakeFiles/hello.dir/main.cxx.o.ddi -MD -MF CMakeFiles/hello.dir/main.cxx.o.ddi.d > CMakeFiles/hello.dir/main.cxx.o.ddi
[2/8] "/Users/hoffman/Work/modules/llvm16-inst/bin/clang-scan-deps" -format=p1689 -- /Users/hoffman/Work/modules/llvm16-inst/bin/clang++   -std=c++20 -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX13.3.sdk -x c++ /Users/hoffman/Work/modules/blog/foo.cxx -c -o CMakeFiles/foo.dir/foo.cxx.o -MT CMakeFiles/foo.dir/foo.cxx.o.ddi -MD -MF CMakeFiles/foo.dir/foo.cxx.o.ddi.d > CMakeFiles/foo.dir/foo.cxx.o.ddi
[3/8] "/Users/hoffman/Work/My Builds/cmake-ninja/bin/cmake" -E cmake_ninja_dyndep --tdi=CMakeFiles/foo.dir/CXXDependInfo.json --lang=CXX --modmapfmt=clang --dd=CMakeFiles/foo.dir/CXX.dd @CMakeFiles/foo.dir/CXX.dd.rsp
[4/8] "/Users/hoffman/Work/My Builds/cmake-ninja/bin/cmake" -E cmake_ninja_dyndep --tdi=CMakeFiles/hello.dir/CXXDependInfo.json --lang=CXX --modmapfmt=clang --dd=CMakeFiles/hello.dir/CXX.dd @CMakeFiles/hello.dir/CXX.dd.rsp
[5/8] /Users/hoffman/Work/modules/llvm16-inst/bin/clang++   -std=c++20 -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX13.3.sdk -MD -MT CMakeFiles/foo.dir/foo.cxx.o -MF CMakeFiles/foo.dir/foo.cxx.o.d @CMakeFiles/foo.dir/foo.cxx.o.modmap -o CMakeFiles/foo.dir/foo.cxx.o -c /Users/hoffman/Work/modules/blog/foo.cxx
[6/8] /Users/hoffman/Work/modules/llvm16-inst/bin/clang++   -std=c++20 -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX13.3.sdk -MD -MT CMakeFiles/hello.dir/main.cxx.o -MF CMakeFiles/hello.dir/main.cxx.o.d @CMakeFiles/hello.dir/main.cxx.o.modmap -o CMakeFiles/hello.dir/main.cxx.o -c /Users/hoffman/Work/modules/blog/main.cxx
[7/8] : && "/Users/hoffman/Work/My Builds/cmake-ninja/bin/cmake" -E rm -f libfoo.a && /usr/bin/ar qc libfoo.a  CMakeFiles/foo.dir/foo.cxx.o && /Users/hoffman/Work/modules/llvm16-inst/bin/llvm-ranlib libfoo.a && "/Users/hoffman/Work/My Builds/cmake-ninja/bin/cmake" -E touch libfoo.a && :
[8/8] : && /Users/hoffman/Work/modules/llvm16-inst/bin/clang++ -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX13.3.sdk -Wl,-search_paths_first -Wl,-headerpad_max_install_names  CMakeFiles/hello.dir/main.cxx.o -o hello  libfoo.a && :

hoffman@caprica b % ./hello 
hello world

Using C++20 without Modules

Some projects may wish to use C++20 features while avoiding C++ modules since they are not supported by all compilers or CMake generators.  CMake 3.28 and above, in targets using C++20 and above, will scan C++ sources for module imports in any project that has been updated to set policy CMP0155 to NEW.  In order to work with compilers or CMake generators that do not support modules, projects need to be updated to explicitly disable scanning.  For example:

cmake_minimum_required(VERSION 3.28)
project(MyProject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)

Summary

In summary, named modules are ready for developers to start using with CMake and three major compilers MSVC, Clang, and GCC. This is new work, and there will certainly be issues, so please report them as you discover them.  For more information about using C++20 modules from CMake, see the cmake-cxxmodules(7) manual.

32 comments to import CMake; the Experiment is Over!

  1. The example contains the following:

    Turning off extensions avoids and issue with the clang compiler

    set(CMAKE_CXX_EXTENSIONS OFF)

    Can you provide more info and perhaps a link to a bug report about the issue mentioned?

    1. Clang 16 had issues with it. I asked about this and Clang 17 fixed it but there was never an issue to track it.

  2. The example should probably also do set(CMAKE_CXX_STANDARD_REQUIRED YES), since it would fail if the toolchain used didn’t support C++20.

    1. Right now you get this error with or without setting that option with a compiler that does not support modules:

      CMake Error in CMakeLists.txt:
      The target named “foo” has C++ sources that use modules but the compiler
      does not provide a way to discover the import graph dependencies

  3. I tried it with MSVC Version 17.7.4 and also with clang 17.01 in Windows 10 using the CMake master. Builds and runs perfectly. No issues at all. Thanks for a nice simple example!

    1. If xcode adds support for modules, we should be able to generate xcode files that work. Until then, there is not much we can do. For ninja to work, the clang changes will have to be moved into the clang that ships with xcode. Until then, CMake is sort of stuck as far as xcode goes.

      1. Is this an Xcode build issue or compiler issue, as Xcode 15’s compiler is based of of LLVM 16.0.0 as far as I know?

        1. The compiler may support it if that’s the case, but I doubt xcodebuild has gained dyndep-like features without it being a line item in the release notes.

    1. No, import std; has not yet been implemented. The plan is for the compiler to populate a CMake::CXX23 target with the requisite information and have that target provide import std; modules. There are discussions about how compilers/standard libraries should provide information for CMake to use and make such targets.

  4. Header units like import ; work with msvc, but with Clang, it’s failing with the error below. Any way around this ?.
    header file (aka ‘C:\mingw64/include/c++/13.2.0/iostream’) cannot be imported because it is not known to be a header unit

    I can revert back to #include’ing in the global module fragment, but would be nice to go all the way with modules.

  5. Since the current/upcoming/eventual default is to scan, I’m confused as to the current purpose of FILE_SET CXX_MODULES.

    Is it solely backwards compatibility (unnecessary going forward, after CMP0155 defaults to NEW)?
    Or, is it perhaps for currently unimplemented, future use (installing/importing)?
    Or I misunderstand something else.

    Seems like future use, or some other explicit purpose – just confusing.

    1. The fileset is necessary to know enough information about which sources will generate BMIs. It aids in the collator knowing:

      the visibility of the contained module (FILE_SETs require explicit visibilities)
      FILE_SET visibility is also much more applicable (e.g., PUBLIC FILE_SET files are not added to consuming library targets’ source listings like PUBLIC non-FILE_SET sources are)
      we need to tell the Visual Studio generator ahead of time which sources will generate BMIs

      Except for the Visual Studio support, we could detect BMI-generating sources at build time and make it Just Work (though I’d prefer if we assumed they were PRIVATE).

      All of this needs to be better documented in the cmake-cxxmodules(7) manpage.

  6. In public-req-private example there is CMake Error about defining module priv in private source. Is it expected and every module which is used in primary module interface should be added as public source?

    1. Yes. Consumers of the modules from another project need to be able to access the transitive closure of modules from that target as they need to (in general) build their own BMIs for them.

  7. “Turning off extensions avoids and issue with the clang 16 compiler” should instead be “Turning off extensions avoids an issue with the clang 16 compiler”.

  8. I’m using Clang version 17.0.5 on OpenSUSE Tumbleweed and I’m getting this message from CMake (3.28.20231126)

    CMake Error in CMakeLists.txt:
    The target named “foo” has C++ sources that may use modules, but modules
    are not supported by this generator. See the cmake-cxxmodules(7) manual
    and the CMAKE_CXX_SCAN_FOR_MODULES variable.

  9. I am trying to make the above example work on my ubuntu VM but I can not

    odygrd@odygrd-virtual-machine:~/CLionProjects/moduleplay/build$ CXX=clang++-17 CC=clang-17 cmake -GNinja ..
    — The CXX compiler identification is Clang 17.0.6
    — Detecting CXX compiler ABI info
    — Detecting CXX compiler ABI info – done
    — Check for working CXX compiler: /usr/bin/clang++-17 – skipped
    — Detecting CXX compile features
    — Detecting CXX compile features – done
    — Configuring done (0.5s)
    — Generating done (0.0s)
    — Build files have been written to: /home/odygrd/CLionProjects/moduleplay/build

    odygrd@odygrd-virtual-machine:~/CLionProjects/moduleplay/build$ ninja -v
    [1/8] “CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS-NOTFOUND” -format=p1689 — /usr/bin/clang++-17 -std=c++20 -x c++ /home/odygrd/CLionProjects/moduleplay/foo.cxx -c -o CMakeFiles/foo.dir/foo.cxx.o -MT CMakeFiles/foo.dir/foo.cxx.o.ddi -MD -MF CMakeFiles/foo.dir/foo.cxx.o.ddi.d > CMakeFiles/foo.dir/foo.cxx.o.ddi.tmp && mv CMakeFiles/foo.dir/foo.cxx.o.ddi.tmp CMakeFiles/foo.dir/foo.cxx.o.ddi
    FAILED: CMakeFiles/foo.dir/foo.cxx.o.ddi
    “CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS-NOTFOUND” -format=p1689 — /usr/bin/clang++-17 -std=c++20 -x c++ /home/odygrd/CLionProjects/moduleplay/foo.cxx -c -o CMakeFiles/foo.dir/foo.cxx.o -MT CMakeFiles/foo.dir/foo.cxx.o.ddi -MD -MF CMakeFiles/foo.dir/foo.cxx.o.ddi.d > CMakeFiles/foo.dir/foo.cxx.o.ddi.tmp && mv CMakeFiles/foo.dir/foo.cxx.o.ddi.tmp CMakeFiles/foo.dir/foo.cxx.o.ddi
    /bin/sh: 1: CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS-NOTFOUND: not found
    [2/8] “CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS-NOTFOUND” -format=p1689 — /usr/bin/clang++-17 -std=c++20 -x c++ /home/odygrd/CLionProjects/moduleplay/main.cxx -c -o CMakeFiles/hello.dir/main.cxx.o -MT CMakeFiles/hello.dir/main.cxx.o.ddi -MD -MF CMakeFiles/hello.dir/main.cxx.o.ddi.d > CMakeFiles/hello.dir/main.cxx.o.ddi.tmp && mv CMakeFiles/hello.dir/main.cxx.o.ddi.tmp CMakeFiles/hello.dir/main.cxx.o.ddi
    FAILED: CMakeFiles/hello.dir/main.cxx.o.ddi
    “CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS-NOTFOUND” -format=p1689 — /usr/bin/clang++-17 -std=c++20 -x c++ /home/odygrd/CLionProjects/moduleplay/main.cxx -c -o CMakeFiles/hello.dir/main.cxx.o -MT CMakeFiles/hello.dir/main.cxx.o.ddi -MD -MF CMakeFiles/hello.dir/main.cxx.o.ddi.d > CMakeFiles/hello.dir/main.cxx.o.ddi.tmp && mv CMakeFiles/hello.dir/main.cxx.o.ddi.tmp CMakeFiles/hello.dir/main.cxx.o.ddi
    /bin/sh: 1: CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS-NOTFOUND: not found
    ninja: build stopped: subcommand failed.

    odygrd@odygrd-virtual-machine:~/CLionProjects/moduleplay/build$ cmake –version
    cmake version 3.28.1

    CMake suite maintained and supported by Kitware (kitware.com/cmake).
    odygrd@odygrd-virtual-machine:~/CLionProjects/moduleplay/build$ ninja –version
    1.11.1

    Any idea how to fix CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS-NOTFOUND error ?

    1. clang-scan-deps should be an executable that is built with clang++-17. Can you look in your /usr/bin? My guess is that it is called /usr/bin/clang-scan-deps-?? and CMake is not finding it. Can you report back as to what it is called? Or if it is there?

      1. I can confirm this. You may have to do: sudo apt-get install clang-17. To find the compiler in C++ look for /usr/bin/clang-17.

  10. I tried replacing the line of code “#include ” with “import ;” in foo.cxx, but got the following error:
    error: header file (aka ‘/usr/bin/../lib/gcc/x86_64-linux-gnu/13/../../../../include/c++/13/iostream’) cannot be imported because it is not known to be a header unit

    Why?

  11. Is there an ongoing work to support #import std;. My understanding is that the build system needs to build the standard library modules, now that CMake supports modules and MSVC and Clang have some support. Where can we track the progress?

    1. Not yet, but it is on the todo list for early Q1 -Q2 2024. Not sure where to track the progress other than watch commits and merge requests.

  12. Your example works, however if I add another library add_library(a a.hpp a.cpp), that does nothing but wrap what you have in main in another function I have some problems.

    target_link_libraries(a foo)
    target_link_libraries(hello a)

    Does work, while target_link_libraries(hello a foo) does not. As far as I know these are equivalent for normal libraries. But for modules there seems to be a property, that does not get passed along this way. Is this intentional, is this a bug, am I doing something wrong?

    1. The working instance has “hello knows about a” and “a knows about foo”. The not-working instance has “hello knows about a” and “hello knows about foo”. Since main’s content (which needs to know about foo) is now in a, you’re missing the “a knows about foo” link.

  13. I modified the example to test re-exporting imports. I created a file bar.cpp:

    export module bar;

    import foo;
    export import foo;

    export void doNothing() {}

    Then modified main.cpp to use import bar; and calling doNothing(); for good measure.
    The compilation has no problem but the linking gives me an error:

    [9/9] Linking CXX executable module_test
    FAILED: module_test
    : && /opt/homebrew//Cellar/llvm/17.0.6_1/bin/clang++ -O3 -DNDEBUG -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk -Wl,-search_paths_first -Wl,-headerpad_max_install_names CMakeFiles/module_test.dir/module_test.cpp.o -o module_test libfoo.a && :
    Undefined symbols for architecture arm64:
    "initializer for module foo (.2)", referenced from:
    initializer for module bar in libfoo.a[3](bar.cpp.o)
    ld: symbol(s) not found for architecture arm64
    clang++: error: linker command failed with exit code 1 (use -v to see invocation)
    ninja: build stopped: subcommand failed.

Leave a Reply