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)