# Issues with transitive usage of C++ Modules

**URL:** https://discourse.cmake.org/t/issues-with-transitive-usage-of-c-modules/15586
**Category:** Code
**Created:** [March 30, 2026, 9:30pm UTC](https://discourse.cmake.org/t/issues-with-transitive-usage-of-c-modules/15586 "2026-03-30T21:30:20Z")
**Posts on this page:** 2
**Page:** 1

<div class="post-metadata">

### Author: ![Nukular1](https://discourse.cmake.org/letter_avatar_proxy/v4/letter/n/e36b37/32.png) [@Nukular1](https://discourse.cmake.org/u/Nukular1)
#### Post date: [March 30, 2026, 9:30pm UTC](https://discourse.cmake.org/t/issues-with-transitive-usage-of-c-modules/15586/1 "2026-03-30T21:30:20Z")

</div>

Hi,

I’ve been experimenting with the current C++20 modules implementation lately in CMake 4.3 and ran into some issues when consuming modules transitively, across multiple intermediate (imported) library targets. It’s very well possible that I’m misunderstanding how this is supposed to work, but I’d appreciate any pointers in the right direction in this case.

If you just want a quick example of what I mean, [the minimal repro cases in this repository should work out of the box](https://gitlab.com/Nukular1/cmakecxxmodulesexperiments) (see `repro/cmake-transitive-usage` subdirectory).

**Issue 1**  
Consider the following dependency structure:  
`LibraryA (installed, exports MyModule) ← LibraryB (imports MyModule) ← MyExe (imports MyModule)`  
The arrows represent publicly linking the CMake targets via `target_link_libraries()`.

**TL;DR:** `LibraryB` works, but `MyExe` doesn’t and can’t find `MyModule` even though I think there should be a transitive usage requirement.

> **Detailed description**
>
> `LibraryA` has been installed with CMake, including the `FILE_SET TYPE CXX_MODULES` with primary module interface `MyModule.cppm` containing `export module MyModule`, which is imported by both `LibraryB` and `MyExe`. This works fine for `LibraryB` , which is able to locate the module interface and generate its BMI, but `MyExe` fails to build with `fatal error: module ‘MyModule’ not found` in both latest GCC and Clang.
> 
> This surprises me - I would expect that the module information is propagated transitively from the imported `LibraryA` to `MyExe`, and this is in fact the case if I have all three targets in the same buildtree without an installation step and imported target in-between. The installed export files of `LibraryA` (so all the `ProjectAConfig` and `Targets` and `cxx-modules` files) look reasonable to me and contain the necessary `IMPORTED_CXX_MODULES_<CONFIG>` entries for the module interface. Looking through the generated CMake files, I can find a reference to the synth target containing the module in `LibraryB`’s `CXXDependInfo.json` and the modmap file, but it’s mentioned in neither for `MyExe`.

> **Reduced CMake Example**
>
> ```cmake
> ### LibraryA CMakeLists.txt
> 
> add_library(LibraryA STATIC)
> target_sources(LibraryA PRIVATE src/MyModule.cpp)
> target_sources(LibraryA PUBLIC
> FILE_SET CXX_MODULES
> TYPE CXX_MODULES
> FILES modules/MyModule.cppm
> )
> target_compile_features(LibraryA PUBLIC cxx_std_23)
> 
> include(GNUInstallDirs)
> set(CMAKE_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/LibraryA")
> 
> install(TARGETS LibraryA
> EXPORT LibraryATargets
> FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_DIR}/modules
> )
> # Create and install package config files here...
> install(EXPORT LibraryATargets
> DESTINATION ${CMAKE_INSTALL_DIR}
> CXX_MODULES_DIRECTORY modules
> )
> 
> ### LibraryB CMakeLists.txt
> 
> find_package(LibraryA REQUIRED)
> 
> add_library(LibraryB SHARED src/Library.h src/Library.cpp)
> target_compile_features(LibraryB PUBLIC cxx_std_23)
> # Works: LibraryA exposes location of MyModule, which is discovered, built and linked successfully
> target_link_libraries(LibraryB PUBLIC ProjectA)
> 
> add_executable(MyExe src/Main.cpp)
> # Doesn't work: MyModule not found, even though it should be transitively "known" via LibraryB
> target_link_libraries(MyExe PUBLIC LibraryB)
> 
> ```

Is this a bug, am I doing something wrong or is it just not working the way I think it does?

**Issue 2**  
I like to use intermediate object libraries for shared library projects to make their non-exported symbols testable - so basically, the object library compiles all the code once, and then the shared library and test executable just link it together with the tests on top. Common settings/flags/usage requirements can then be set via an Interface target, for example.

So this time our dependency structure is the following:  
`ObjLibA (exports MyModule) ← SharedLibA (installed) ← MyExe (imports MyModule)`

**TL;DR** : This is more of a usability question: Can I make this work _without_ installing `ObjLibA`, which I could do with headers, but not with modules?

> **Detailed description**
>
> I didn’t find any way to make the module import work. With headers, I would just use `target_link_libraries(SharedLibA PRIVATE $<TARGET_OBJECTS:ObjLibA>)` and add the headers to `SharedLibA` via an `INTERFACE` or `PUBLIC` file set to make sure they are installed properly. However, `CXX_MODULES` file sets do not allow `INTERFACE` visibility, and using a `PUBLIC` one leads to compiling the primary module interface twice (resulting in error `multiple definition of 'initializer for module MyModule'` in GCC).
> 
> Linking publicly to `ObjLibA` would propagate the module usage requirement properly to `SharedLibA`, but then the `install(EXPORT)` command fails if I dont’t install the object library as well (makes sense and fair enough, but it is an implementation detail that I’d like to keep private). And even if I install it, I run into issue 1 again, since `MyExe` now again can’t consume the module transitively from the imported `ObjLibA` target.

> **Reduced CMake Example**
>
> ```cmake
> ### LibraryA CMakeLists.txt
> 
> add_library(ObjLibA OBJECT)
> target_sources(ObjLibA PRIVATE src/MyModule.cpp modules/MyModule.cppm)
> target_sources(ObjLibA PUBLIC
> FILE_SET CXX_MODULES
> TYPE CXX_MODULES
> FILES modules/MyModule.cppm
> )
> target_compile_features(ObjLibA PUBLIC cxx_std_23)
> 
> add_library(SharedLibA SHARED)
> target_sources(SharedLibA PRIVATE src/Empty.cpp)
> # Leads to compilation errors due to compiling MyModule twice
> target_sources(SharedLibA PUBLIC
> FILE_SET CXX_MODULES
> TYPE CXX_MODULES
> FILES modules/MyModule.cppm
> )
> target_compile_features(SharedLibA PUBLIC cxx_std_23)
> target_link_libraries(SharedLibA PRIVATE $<TARGET_OBJECTS:ObjLibA>)
> # Alternatives I looked into:
> # target_sources(SharedLibA PRIVATE $<TARGET_OBJECTS:ObjLibA>) # Same error
> # target_link_libraries(SharedLibA PUBLIC ObjLibA) # Requires installing ObjLibA
> 
> include(GNUInstallDirs)
> set(CMAKE_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/LibraryA")
> 
> install(TARGETS LibraryA
> EXPORT LibraryATargets
> FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_DIR}/modules
> )
> # Create and install package config files here...
> install(EXPORT LibraryATargets
> DESTINATION ${CMAKE_INSTALL_DIR}
> CXX_MODULES_DIRECTORY modules
> )
> 
> ### LibraryB CMakeLists.txt
> 
> find_package(LibraryA REQUIRED)
> 
> add_executable(MyExe src/Main.cpp)
> target_compile_features(MyExe PUBLIC cxx_std_23)
> target_link_libraries(MyExe PUBLIC LibraryA)
> 
> ```

Is there another way to make this work that I’m overlooking?  
It seems to me that having `INTERFACE` visibility available for `CXX_MODULES` file sets would help a lot with these kinds of problems and give us more freedom ([see also this recent topic](https://discourse.cmake.org/t/interface-library-target-and-cxx-modules-file-set/15550)), but I imagine this might be tricky to implement with all the BMI mapping in the background.

---

<div class="post-metadata">

### Author: ![ClausKlein](https://discourse.cmake.org/user_avatar/discourse.cmake.org/clausklein/32/352_2.png) [@ClausKlein](https://discourse.cmake.org/u/ClausKlein)
#### Post date: [April 8, 2026, 8:36pm UTC](https://discourse.cmake.org/t/issues-with-transitive-usage-of-c-modules/15586/2 "2026-04-08T20:36:16Z")

</div>

_ **Quickfix** _ for Issue 1:

```cmake
target_link_libraries(ProjectB_Exe PUBLIC ProjectA::ProjectA)

```

@vito.gamberini @brad.king is this a feature or a bug?
