C++20 modules API example

The recent Kitware CMake blog post on C++20 modules gives a standalone example for MSVC, GCC and Clang.

Note that CMake >= 3.25.3 (i.e. Nightly) is necessary for this to work. And to make it simple use MSVC if on Windows.

standalone CMakeLists.txt:

cmake_minimum_required(VERSION 3.25.3)
project(std_module_example CXX)
# https://www.kitware.com/import-cmake-c20-modules/

set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "2182bf5c-ef0d-489a-91da-49dbc3090d2a")

if(NOT MSVC)
  message(FATAL_ERROR "only for MSVC for now.")
endif()

set(CMAKE_CXX_STANDARD 20)

file(WRITE foo.h
"class foo {
foo();
~foo();
void helloworld();
};"
)

file(WRITE foo.cxx
[=[
#include "foo.h"
#include <iostream>
foo::foo() = default;
foo::~foo() = default;
void foo::helloworld() { std::cout << "hello world\n"; }
]=]
)

file(WRITE main.cxx
[=[
#include "foo.h"

int main()
{
  foo f;
  f.helloworld();
  return 0;
}
]=]
)

add_library(foo)
target_sources(foo
  PUBLIC
    FILE_SET cxx_modules TYPE CXX_MODULES FILES
    foo.cxx
)
add_executable(hello main.cxx)

Sorry, something is wrong with this example?

bash-3.2$ cmake --version
cmake version 3.26.3

CMake suite maintained and supported by Kitware (kitware.com/cmake).
bash-3.2$ ninja --version
1.11.1.git.kitware.jobserver-1

bash-3.2$ cmake -B build -G Ninja
-- The CXX compiler identification is AppleClang 14.0.3.14030022
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Warning (dev) at CMakeLists.txt:77 (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.

-- Configuring done (0.6s)
CMake Warning (dev):
  C++20 modules support via CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP is
  experimental.  It is meant only for compiler developers to try.
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Generating done (0.0s)
-- Build files have been written to: /Users/clausklein/Workspace/cpp/cxx20/test/build
bash-3.2$ ninja -C build
ninja: Entering directory `build'
[3/8] Generating CXX dyndep file CMakeFiles/foo.dir/CXX.dd
FAILED: CMakeFiles/foo.dir/CXX.dd 
/usr/local/Cellar/cmake/3.26.3/bin/cmake -E cmake_ninja_dyndep --tdi=CMakeFiles/foo.dir/CXXDependInfo.json --lang=CXX --dd=CMakeFiles/foo.dir/CXX.dd @CMakeFiles/foo.dir/CXX.dd.rsp
CMake Error: -E cmake_ninja_dyndep failed to parse CMakeFiles/foo.dir/foo.cxx.o.ddi* Line 1, Column 1
  Syntax error: value, object or array expected.

CMake Error: -E cmake_ninja_dyndep failed to parse ddi file CMakeFiles/foo.dir/foo.cxx.o.ddi
ninja: build stopped: subcommand failed.
bash-3.2$ 

This example works with clang++ v16.0.1 on OSX

But for clang-tidy I have to set the -fprebuilt-module-path=${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/foo.dir

cmake_minimum_required(VERSION 3.26)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

project(clang-tidy-issue LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)

# First find clang-tidy, this also allows users to provide hints
find_program(CLANG_TIDY NAMES clang-tidy REQUIRED)

# https://clang.llvm.org/docs/StandardCPlusPlusModules.html#how-to-build-projects-using-modules
# clang++ -std=c++20 -x c++-module foo.cxx --precompile -o build/foo.pcm
# clang++ -std=c++20 main.cxx -fprebuilt-module-path=build build/foo.pcm -o build/Hello
#
# see https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.3/Help/dev/experimental.rst?ref_type=tags#c-20-module-apis
# and https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.3/.gitlab/ci/cxx_modules_rules_gcc.cmake?ref_type=tags
#     https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.3/.gitlab/ci/cxx_modules_rules_clang.cmake?ref_type=tags

set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API 2182bf5c-ef0d-489a-91da-49dbc3090d2a)
set(CMake_TEST_CXXModules_UUID "a246741c-d067-4019-a8fb-3d16b0c9d1d3")

set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  string(CONCAT CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE
                "<CMAKE_CXX_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -E -x c++ <SOURCE>"
                " -MT <DYNDEP_FILE> -MD -MF <DEP_FILE>"
                " -fmodules-ts -fdep-file=<DYNDEP_FILE> -fdep-output=<OBJECT> -fdep-format=trtbd"
                " -o <PREPROCESSED_SOURCE>"
  )
  set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "gcc")
  set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG "-fmodules-ts -fmodule-mapper=<MODULE_MAP_FILE> -fdep-format=trtbd -x c++")

  set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY};-checks=*,-llvmlibc-*,-fuchsia-*,-cppcoreguidelines-init-variables")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
  set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "clang")
  # Maby you must set C++ extensions being off. Clang's modules support may have trouble with extensions.
  #XXX set(CMAKE_CXX_EXTENSIONS OFF)

  # Then I have this which is essentially calls clang-tidy with warnings as errors (For CI)
  set(CMAKE_CXX_CLANG_TIDY
      "${CLANG_TIDY};-extra-arg=-fprebuilt-module-path=${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/foo.dir"
  )
endif()

file(WRITE foo.cxx
     [=[
// 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() = default;
  ~foo() = default;
  void helloworld();
};

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

file(WRITE main.cxx
     [=[
import foo;

auto main() -> int
{
  foo myFoo;
  myFoo.helloworld();
  return 0;
}
]=]
)

add_library(foo)
target_sources(
  foo
  PUBLIC FILE_SET
         cxx_modules
         TYPE
         CXX_MODULES
         FILES
         foo.cxx
)
add_executable(hello main.cxx)
target_link_libraries(hello foo)

install(TARGETS foo ARCHIVE PUBLIC_HEADER
  FILE_SET cxx_modules DESTINATION include
  CXX_MODULES_BMI DESTINATION share/cxx-modules
)
include(cpack)

I am trying to get import std + clang tidy working together nicely with clang 19 on Fedora, having some trouble adapting this to work with my project.

CMakeLists.txt:

cmake_minimum_required(VERSION 3.30.0 FATAL_ERROR)

set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "0e5b6991-d74f-4b3d-a41c-cf096e0b2508")
set(CMAKE_CXX_MODULE_STD ON)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_EXTENSIONS OFF)

find_program(CLANG_TIDY NAMES clang-tidy REQUIRED)
set(CMAKE_CXX_CLANG_TIDY
    "${CLANG_TIDY};-extra-arg=-fprebuilt-module-path=${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/example.dir"
)


project(example LANGUAGES CXX)

add_executable(example)
target_sources(example
  PRIVATE FILE_SET CXX_MODULES FILES
  example.cppm
)

example.cppm:

export module example;

import std;

auto main() -> int
{
        std::println("Hello World");
}

Here’s the result:

cmake \
        -S . \
        -B build \
        -G "Ninja" \
        -DCMAKE_CXX_COMPILER=clang++ \
        -DCMAKE_CXX_FLAGS=-stdlib=libc++
-- Configuring done (0.0s)
CMake Warning (dev) in CMakeLists.txt:
  CMake's support for `import std;` in C++23 and newer 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.

-- Generating done (0.0s)
-- Build files have been written to: /home/rd8/tmp/modules/build
cmake \
        --build build \
        -t all \
        -v \
        -j
Change Dir: '/home/rd8/tmp/modules/build'

Run Build Command(s): /usr/bin/ninja-build -v all
[1/7] /var/lib/snapd/snap/cmake/1445/bin/cmake -E cmake_ninja_dyndep --tdi=CMakeFiles/__cmake_cxx23.dir/CXXDependInfo.json --lang=CXX --modmapfmt=clang --dd=CMakeFiles/__cmake_cxx23.dir/CXX.dd @CMakeFiles/__cmake_cxx23.dir/CXX.dd.rsp
[2/7] /var/lib/snapd/snap/cmake/1445/bin/cmake -E cmake_ninja_dyndep --tdi=CMakeFiles/example.dir/CXXDependInfo.json --lang=CXX --modmapfmt=clang --dd=CMakeFiles/example.dir/CXX.dd @CMakeFiles/example.dir/CXX.dd.rsp
[3/7] /var/lib/snapd/snap/cmake/1445/bin/cmake -E __run_co_compile --tidy="/usr/bin/clang-tidy;-extra-arg=-fprebuilt-module-path=/home/rd8/tmp/modules/build/CMakeFiles/example.dir;--extra-arg-before=--driver-mode=g++" --source=/usr/share/libc++/v1/std.cppm -- /usr/bin/clang++  -I/usr/bin/../lib/gcc/x86_64-redhat-linux/14/../../../../lib64/../share/libc++/v1 -stdlib=libc++ -std=c++23 -Wno-reserved-module-identifier -MD -MT CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.cppm.o -MF CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.cppm.o.d @CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.cppm.o.modmap -o CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.cppm.o -c /usr/share/libc++/v1/std.cppm
[4/7] /var/lib/snapd/snap/cmake/1445/bin/cmake -E __run_co_compile --tidy="/usr/bin/clang-tidy;-extra-arg=-fprebuilt-module-path=/home/rd8/tmp/modules/build/CMakeFiles/example.dir;--extra-arg-before=--driver-mode=g++" --source=/home/rd8/tmp/modules/example.cppm -- /usr/bin/clang++   -stdlib=libc++ -std=c++23 -MD -MT CMakeFiles/example.dir/example.cppm.o -MF CMakeFiles/example.dir/example.cppm.o.d @CMakeFiles/example.dir/example.cppm.o.modmap -o CMakeFiles/example.dir/example.cppm.o -c /home/rd8/tmp/modules/example.cppm
FAILED: CMakeFiles/example.dir/example.cppm.o CMakeFiles/example.dir/example.pcm
/var/lib/snapd/snap/cmake/1445/bin/cmake -E __run_co_compile --tidy="/usr/bin/clang-tidy;-extra-arg=-fprebuilt-module-path=/home/rd8/tmp/modules/build/CMakeFiles/example.dir;--extra-arg-before=--driver-mode=g++" --source=/home/rd8/tmp/modules/example.cppm -- /usr/bin/clang++   -stdlib=libc++ -std=c++23 -MD -MT CMakeFiles/example.dir/example.cppm.o -MF CMakeFiles/example.dir/example.cppm.o.d @CMakeFiles/example.dir/example.cppm.o.modmap -o CMakeFiles/example.dir/example.cppm.o -c /home/rd8/tmp/modules/example.cppm
/home/rd8/tmp/modules/example.cppm:3:8: error: module 'std' not found [clang-diagnostic-error]
    3 | import std;
      | ~~~~~~~^~~
1 error generated.
Error while processing /home/rd8/tmp/modules/example.cppm.
Found compiler error(s).
[5/7] /var/lib/snapd/snap/cmake/1445/bin/cmake -E __run_co_compile --tidy="/usr/bin/clang-tidy;-extra-arg=-fprebuilt-module-path=/home/rd8/tmp/modules/build/CMakeFiles/example.dir;--extra-arg-before=--driver-mode=g++" --source=/usr/share/libc++/v1/std.compat.cppm -- /usr/bin/clang++  -I/usr/bin/../lib/gcc/x86_64-redhat-linux/14/../../../../lib64/../share/libc++/v1 -stdlib=libc++ -std=c++23 -Wno-reserved-module-identifier -MD -MT CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.compat.cppm.o -MF CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.compat.cppm.o.d @CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.compat.cppm.o.modmap -o CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.compat.cppm.o -c /usr/share/libc++/v1/std.compat.cppm
FAILED: CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.compat.cppm.o CMakeFiles/__cmake_cxx23.dir/std.compat.pcm
/var/lib/snapd/snap/cmake/1445/bin/cmake -E __run_co_compile --tidy="/usr/bin/clang-tidy;-extra-arg=-fprebuilt-module-path=/home/rd8/tmp/modules/build/CMakeFiles/example.dir;--extra-arg-before=--driver-mode=g++" --source=/usr/share/libc++/v1/std.compat.cppm -- /usr/bin/clang++  -I/usr/bin/../lib/gcc/x86_64-redhat-linux/14/../../../../lib64/../share/libc++/v1 -stdlib=libc++ -std=c++23 -Wno-reserved-module-identifier -MD -MT CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.compat.cppm.o -MF CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.compat.cppm.o.d @CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.compat.cppm.o.modmap -o CMakeFiles/__cmake_cxx23.dir/usr/share/libc++/v1/std.compat.cppm.o -c /usr/share/libc++/v1/std.compat.cppm
/usr/share/libc++/v1/std.compat.cppm:96:15: error: module 'std' not found [clang-diagnostic-error]
   96 | export import std;
      |        ~~~~~~~^~~
1 error generated.
Error while processing /usr/share/libc++/v1/std.compat.cppm.
Found compiler error(s).
ninja: build stopped: subcommand failed.

It works if I remove the clang tidy stuff.

Same problems without import std;

cmake_minimum_required(VERSION 3.30...4.0)

set(CMAKE_EXPORT_COMPILE_COMMANDS YES)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_EXTENSIONS YES)
set(CMAKE_CXX_STANDARD_REQUIRED YES)

set(CMAKE_CXX_SCAN_FOR_MODULES YES)

project(FetchLibTestReceive VERSION 1.0 LANGUAGES CXX)

# First find clang-tidy, this also allows users to provide hints
# FIXME: find_program(CMAKE_CXX_CLANG_TIDY NAMES clang-tidy REQUIRED)

file(WRITE greeting.cppm
     [=[
// Global Module Fragment (GMF) where #includes can happen
module;
#include <print>
#include <string>

// first thing after the Global module fragment must be a module command
// XXX import std;

export module greeting;

export class greeting {
  public:
    explicit greeting(const std::string_view name) : m_name(name) {}
    ~greeting() = default;
    void helloworld();

  private:
    std::string m_name;
};

void greeting::helloworld() {
    std::print("hello world from {}\n", m_name);
}
]=]
)

file(WRITE hello.cpp
     [=[
import greeting;

auto main() -> int {
    greeting greeter("Claus");
    greeter.helloworld();

    return 0;
}
]=]
)

add_library(greeting)
target_sources(
  greeting
  PUBLIC FILE_SET
         cxx_modules
         TYPE
         CXX_MODULES
         FILES
         greeting.cppm
)
add_executable(hello hello.cpp)
target_link_libraries(hello greeting)

enable_testing()
add_test(NAME hello COMMAND hello)

add_test(NAME test-simple_module COMMAND test-simple_module)
install(TARGETS greeting ARCHIVE PUBLIC_HEADER FILE_SET cxx_modules DESTINATION include CXX_MODULES_BMI
                                               DESTINATION libexec/cpp-modules
)

but run-clang-tidy from console works fine:

run-clang-tidy -p out/build/release -checks='-*,hicpp-*'
Enabled checks:
    hicpp-avoid-c-arrays
    hicpp-avoid-goto
    hicpp-braces-around-statements
    hicpp-deprecated-headers
    hicpp-exception-baseclass
    hicpp-explicit-conversions
    hicpp-function-size
    hicpp-ignored-remove-result
    hicpp-invalid-access-moved
    hicpp-member-init
    hicpp-move-const-arg
    hicpp-multiway-paths-covered
    hicpp-named-parameter
    hicpp-new-delete-operators
    hicpp-no-array-decay
    hicpp-no-assembler
    hicpp-no-malloc
    hicpp-noexcept-move
    hicpp-signed-bitwise
    hicpp-special-member-functions
    hicpp-static-assert
    hicpp-undelegated-constructor
    hicpp-uppercase-literal-suffix
    hicpp-use-auto
    hicpp-use-emplace
    hicpp-use-equals-default
    hicpp-use-equals-delete
    hicpp-use-noexcept
    hicpp-use-nullptr
    hicpp-use-override
    hicpp-vararg

Running clang-tidy for 2 files out of 2 in compilation database ...
[1/2][1.1s] /usr/local/opt/llvm/bin/clang-tidy -checks=-*,hicpp-* -p=out/build/release /Users/clausklein/Workspace/cpp/cxx20/test/hello.cpp
1772 warnings generated.
Suppressed 1772 warnings (1772 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.

[2/2][1.6s] /usr/local/opt/llvm/bin/clang-tidy -checks=-*,hicpp-* -p=out/build/release /Users/clausklein/Workspace/cpp/cxx20/test/greeting.cppm
/Users/clausklein/Workspace/cpp/cxx20/test/greeting.cppm:11:14: warning: class 'greeting' defines a default destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator [hicpp-special-member-functions]
   11 | export class greeting {
      |              ^
1772 warnings generated.
Suppressed 1771 warnings (1771 in non-user code).
Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well.

bash-5.2$