Compile C++20 Modules with clang++-16 and CMake v3.26.3

I have a question about the experimental CMake support for C++ Modules with clang++ on OSX.

According this Standard C++ Modules — Clang 17.0.0git documentation, it
is quit simple to compile and link a C++ Module example and very fast:

clang++ -std=c++20 -x c++-module greeting.cppm --precompile -o build/greeting.pcm
clang++ -std=c++20 hello.cpp -fprebuilt-module-path=build build/greeting.pcm -o build/hello

Note: The PCM file ist linked!

But with CMake this is done, which is much more complicated and slow:

bash-3.2$ ninja -v hello 

[1/8] "/usr/local/Cellar/llvm/16.0.3/bin/clang-scan-deps" -format=p1689 -- /usr/local/opt/llvm/bin/clang-16   -std=c++20
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk -x c++
/Users/clausklein/Workspace/cpp/cxx20/test/hello.cpp -c -o CMakeFiles/hello.dir/hello.cpp.o -MT
CMakeFiles/hello.dir/hello.cpp.o.ddi -MD -MF CMakeFiles/hello.dir/hello.cpp.o.ddi.d > CMakeFiles/hello.dir/hello.cpp.o.ddi

[2/8] "/usr/local/Cellar/llvm/16.0.3/bin/clang-scan-deps" -format=p1689 -- /usr/local/opt/llvm/bin/clang-16   -std=c++20
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk -x c++
/Users/clausklein/Workspace/cpp/cxx20/test/greeting.cppm -c -o CMakeFiles/greeting.dir/greeting.cppm.o -MT
CMakeFiles/greeting.dir/greeting.cppm.o.ddi -MD -MF CMakeFiles/greeting.dir/greeting.cppm.o.ddi.d >
CMakeFiles/greeting.dir/greeting.cppm.o.ddi

[3/8] /usr/local/Cellar/cmake/3.26.3/bin/cmake -E cmake_ninja_dyndep --tdi=CMakeFiles/greeting.dir/CXXDependInfo.json
--lang=CXX --modmapfmt=clang --dd=CMakeFiles/greeting.dir/CXX.dd @CMakeFiles/greeting.dir/CXX.dd.rsp

[4/8] /usr/local/opt/llvm/bin/clang-16   -std=c++20 -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk -MD -MT
CMakeFiles/greeting.dir/greeting.cppm.o -MF CMakeFiles/greeting.dir/greeting.cppm.o.d
@CMakeFiles/greeting.dir/greeting.cppm.o.modmap -o CMakeFiles/greeting.dir/greeting.cppm.o -c
/Users/clausklein/Workspace/cpp/cxx20/test/greeting.cppm @CMakeFiles/greeting.dir/greeting.cppm.o.modmap

clang-16: warning: '-x c++-module' after last input file has no effect [-Wunused-command-line-argument]

[5/8] : && /usr/local/Cellar/cmake/3.26.3/bin/cmake -E rm -f libgreeting.a && /usr/bin/ar qc libgreeting.a
CMakeFiles/greeting.dir/greeting.cppm.o && /usr/local/opt/llvm/bin/llvm-ranlib libgreeting.a &&
/usr/local/Cellar/cmake/3.26.3/bin/cmake -E touch libgreeting.a && :

[6/8] /usr/local/Cellar/cmake/3.26.3/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

[7/8] /usr/local/opt/llvm/bin/clang-16   -std=c++20 -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk -MD -MT
CMakeFiles/hello.dir/hello.cpp.o -MF CMakeFiles/hello.dir/hello.cpp.o.d @CMakeFiles/hello.dir/hello.cpp.o.modmap -o
CMakeFiles/hello.dir/hello.cpp.o -c /Users/clausklein/Workspace/cpp/cxx20/test/hello.cpp
@CMakeFiles/hello.dir/hello.cpp.o.modmap

[8/8] : && /usr/local/opt/llvm/bin/clang-16 -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk -Wl,-search_paths_first
-Wl,-headerpad_max_install_names -lstdc++ CMakeFiles/hello.dir/hello.cpp.o -o hello  libgreeting.a && :

bash-3.2$ find . -name '*.modmap' | xargs head
==> ./CMakeFiles/greeting.dir/greeting.cppm.o.modmap <==
-x c++-module
-fmodule-output=CMakeFiles/greeting.dir/greeting.pcm

==> ./CMakeFiles/hello.dir/hello.cpp.o.modmap <==
-fmodule-file=greeting=CMakeFiles/greeting.dir/greeting.pcm
bash-3.2$

Note: The object library is linked and I need use -lstdc++?

This is my CMakeLists.txt:


set(CMAKE_EXPORT_COMPILE_COMMANDS YES)

project(clang-tidy-issue LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_EXTENSIONS NO)
set(CMAKE_CXX_STANDARD_REQUIRED YES)

set(CMAKE_CXX_SCAN_FOR_MODULES YES)

# 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 greeting.cppm --precompile -o build/greeting.pcm
# clang++ -std=c++20 hello.cpp -fprebuilt-module-path=build build/greeting.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")
# Workaround for C++Modules: Missing module map flags in exported compile commands
# see https://gitlab.kitware.com/cmake/cmake/-/issues/24618
set(CMAKE_CXX_COMPILE_OBJECT "${CMAKE_CXX_COMPILE_OBJECT} @<OBJECT>.modmap")

set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP 1)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
  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")
  set(CMAKE_EXE_LINKER_FLAGS -lstdc++)
endif()

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

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

export module greeting;

export class greeting {
public:
  greeting() = default;
  ~greeting() = default;
  void helloworld();
};

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

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

auto main() -> int
{
  greeting greeter;
  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)
1 Like

This is because you know the module name and requirements. CMake cannot generate command lines like this because for any given source:

  • it does not know what module it provides (only that it provides one)
  • it does not know what modules it requires

CMake could do this if we did scanning as part of configure/generate. However, that then means that CMake needs to reconfigure/generate on any change to such files because it could, theoretically, change the set of provides/requires. It may well be slower to compile, but the mechanics of making such commands lines would inevitably be slower because a CMake configure/generate cycle would then be injected into any module-using or module-providing source file edit.

Why is this needed? It’s not needed in my testing or our CI (though I don’t know the exact versions in use).

What do you mean “the object library is linked”? I see no object (OBJECT?) library in your example. There is a greeting library which is linked from hello which does indeed require the libgreeting.a on the link line of hello.

I meaned the object file is used instead of the precompiled Module (pcm) file.

Yes, the object file has what is needed for the library/executable. Your examples are skipping the -c part because they are single-file artifacts; any more source files and you’d need to do the same thing too.

1 Like

Only the module implementation unit
(.cpp) files needs to to by compiled.

Module interfaces can have object code as well, in general.