C++ 20 Modules Update

I’m in the same boat. I’d like to try C++ modules, but only if I can do it with the main branch of clang. Do we have an updated command line for this clang-scan-deps tool?

I just compiled llvm and clang from source, so I have:

./build/bin/clang-scan-deps --version
LLVM (http://llvm.org/):
  LLVM version 17.0.0git
  Optimized build.

See this code. CMake 3.26.0-rc6 and above will set CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE automatically with Clang 16+, but you’ll still need to set CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API and CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP.

1 Like

Great, thanks!

I had another error below with cmake 3.25.2, but once I updated to 3.26 using brew install cmake --HEAD, now it works.

CMake Error: -E cmake_ninja_dyndep does not understand the clang module map format

I’m getting some errors because (I guess) it doesn’t know about new extensions, like .ccm.

I changed my file extension from .cc to .ccm, as instructed by the Clang docs. That caused this error below about the “linker language”. I got past that by adding the set_target_properties.

CMake Error: CMake can not determine linker language for target: learn_modules_lib
add_library(learn_modules_lib)
target_sources(learn_modules_lib
  PUBLIC
    FILE_SET cxx_modules TYPE CXX_MODULES FILES
    Learning/Modules/foo.ccm
)
# 'Fixes' error
set_target_properties(learn_modules_lib PROPERTIES LINKER_LANGUAGE CXX)

Now I get another error when I build…

FAILED: Learning/liblearn_modules_lib.a 
: && /usr/local/Cellar/cmake/HEAD-3d6075d/bin/cmake -E rm -f Learning/liblearn_modules_lib.a && /usr/bin/ar qc Learning/liblearn_modules_lib.a   && /Users/rob/Dev/llvm-project/build/bin/llvm-ranlib Learning/liblearn_modules_lib.a && /usr/local/Cellar/cmake/HEAD-3d6075d/bin/cmake -E touch Learning/liblearn_modules_lib.a && :
ar: no archive members specified
usage:  ar -d [-TLsv] archive file ...
	ar -m [-TLsv] archive file ...
	ar -m [-abiTLsv] position archive file ...
	ar -p [-TLsv] archive [file ...]
	ar -q [-cTLsv] archive file ...
	ar -r [-cuTLsv] archive file ...
	ar -r [-abciuTLsv] position archive file ...
	ar -t [-TLsv] archive [file ...]
	ar -x [-ouTLsv] archive [file ...]
ninja: build stopped: subcommand failed.

CMake MR 5926 added support for .ixx and .cppm, but not .ccm, .cxxm, or .c++m. I’ll look at adding those.

Added in CMake MR 8308, targeting CMake 3.27.

Meanwhile, just use .cpp, .cc, or other classic C++ file extensions for CXX_MODULES file set entries.

I’ve been using Emacs with lsp-mode and clangd for my C++ project. My cmake command contains arguments to export the compile_commands.json file, which clangd then uses. It’s been working great, but now with modules it’s reporting an error, even though everything compiles file.

Screen from Emacs:

The module is found and this file compiles and runs fine.

Any ideas on how to fix this?

This is the cmake command that has been successfully creating the compile_commands.json file for clangd:

cmake -B build-debug -S . -DCMAKE_TOOLCHAIN_FILE=/Users/rob/Dev/vcpkg/scripts/buildsystems/vcpkg.cmake 
   -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -G Ninja -DCMAKE_BUILD_TYPE=Debug

Or .cppm I assume. After your previous post I switched it to that, and it seems to work.

More more thing, it seems .mm (Objective-C++) can’t import modules. I get errors like:

error: Objective-C was disabled in PCH file but is currently enabled
error: module file AppleInterop/CMakeFiles/AppleInterop.dir/AppleInterop.Strings.pcm cannot be loaded due to a configuration mismatch with the current compilation [-Wmodule-file-config-mismatch]
/Users/rob/Workspace/MonoRepo/AppleInterop/AppleInterop/UserDefaults.mm:10:2: error: unknown type name 'include'

If anyone knows how to get around that, please let me know. I need a few of those .mm files, to wrap certain Apple APIs.

Ugh. We have enough extensions floating around. I see no reason for these new ones (it is, after all, just C++ code), but whatever. I don’t forsee progress on getting everyone to agree on one extension (or even a set that I can count on one hand).

compile_commands.json does not have enough information for modules prior to building the project (and before anyone asks, it cannot have enough when using explicit module builds which is all CMake supports). There’s a @rspfile argument we pass to the compiler that does not exist until sources have been scanned and the target’s collation command executed.

I have no idea how this is supposed to work (beyond “ignore that it is Objective-C++ and treat it as C++ code”). Please work with Clang developers to see what Objective-C++ is doing with C++ module support.

I asked them, and learned about these compiler flags as a workaround.

set_source_files_properties(MyLibrary/UserDefaults.mm
  PROPERTIES COMPILE_FLAGS "-Xclang -fno-validate-pch")

What is the correct way to define, in one module, two classes that depend on each other? I tried the code below, and a few variations, but I get the error at the bottom about a cyclic dependency.

UI.cc

export module UI;
export import :Window;
export import :View;

Window.cc

module UI:Window;
import :View;
export class Window {
public:
    Window() {}
    View getMainView() { return View(); }
};

View.cc

module UI:View;
import :Window;
export class View {
public:
    View() {}    
    Window getWindow() {
        return Window();
    }
};
ninja: build stopped: dependency cycle: 
     Learning/CMakeFiles/learn_modules.dir/UI-Window.pcm 
  -> Learning/CMakeFiles/learn_modules.dir/UI-View.pcm 
  -> Learning/CMakeFiles/learn_modules.dir/UI-Window.pcm.

Modules may not import each other (directly or transitively). This is part of the standard. What you can do is put the declarations in the headers (though your interfaces still have a cycle) and put the implementations into a TU looking something like this:

module UI;

Window::Window() {}
View Window::getMainView() { return View(); }

View::View() {}
Window View::getWindow() { return Window(); }

I don’t remember if you need to name the partitions or not (that seems weird to me as this TU imports all of UI and therefore already has it “available”).

Okay, I got that to work by putting the class declarations in the primary module interface unit, and then creating a .cc file with your example code block. I guess that’s called “module implementation unit”.

But, that requires that I declare everything in the primary interface file. In real life I have lots of files, so I want to split them out, and I think that’s what “interface partitions” are for. Those seem to work with raw Clang commands (I can follow their example here), but I can’t get them to work with CMake. Are they supported yet?

If I put the partition implementation files in the CXX_MODULES files set, I get errors like:

ninja: build stopped: multiple rules generate Learning/CMakeFiles/learn_modules.dir/UI-Window.pcm.

If I put them them in the normal file set, I get:

CMake Error: Output Learning/CMakeFiles/learn_modules.dir/Learning/Modules/Vimpl.cc.o provides 
the `UI:View` module but it is not found in a `FILE_SET` of type `CXX_MODULES` (or 
`CXX_MODULE_INTERNAL_PARTITIONS` if it is not `export`ed)

I tried CXX_MODULE_INTERNAL_PARTITIONS but got:

target_sources File set TYPE may only be "HEADERS", "CXX_MODULES", or
"CXX_MODULE_HEADER_UNITS"

Interface partitions work (and are tested). I don’t see a circular dependency anywhere there.

Circular dependencies are not planned to be supported as it is not standard C++. I think it might work with the two-phase compilation Clang has, but Clang is the only compiler to support that model (today) and even then it is known to not have the same behavior as the one-phase mechanism CMake is using (last I heard). Even when CMake supports the two-phase compilation, I’m partial to not allowing circular dependencies.

Sources which do not create a BMI (including module implementation files) belong in the “regular” source lists, not CXX_MODULES file sets.

This is an MSVC extension and not relevant here. I suspect that this will “never” get used in practice.

Thanks! I meant to ask if interface partitions, not circular dependencies, are supported. So that answers my question.

I think I got it working now. I had followed that Clang example, but made one mistake, which caused the error: “multiple rules to generate … UI-Window.pcm”.

My mistake was that I used the same name in the partition-implementation module as in the partition interface. It faked me out because with the non-partitions, their demo code does use the same name in multiple files.

File A: export module M; ... interface stuff ...
File B: module M; ... impl stuff ...

But…

File C: export module M:part; ... interface stuff ...
File D: module M:part_impl; ← Using M:part here causes problems.

Yes, the standard confused a number of people in this regard (and is basically what the MSVC extension is from). Clarity would be nice, but there is no such thing as a “partition implementation unit” despite it being a hole that one would expect to be filled, but is excluded by not being defined at all. Implementation units always just name the module and don’t care about partitions at all.

One thing I just noticed: if I change code after a module :private; line (private module fragment), it triggers recompilation of dependent code. Not sure if that’s a cmake or a clang issue. (?)

CMake’s collator generates a ninja dyndep file that adds restat = 1 to the compilation rule for the module interface unit’s object file. That tells ninja to stat() the outputs again after the compiler runs in order to check which outputs really changed. Ninja should recognize that consumers of unchanged outputs do not need to rebuild.

I tested this locally. When the module interface doesn’t change, clang writes an identical .pcm file, but it still updates the timestamp of the .pcm file. That prevents Ninja from recognizing that the module interface hasn’t changed, so it still recompiles dependents. This is a problem in Clang.

Interesting. I’ve never heard of a compiler not writing its output file because the one it’s replacing is the same. Is that part of how these .pcm files are supposed to work? Is that mentioned in the C++ standard or somewhere? If so, I can file a bug with the LLVM/Clang project, if there isn’t one already.