Clang doesn't see module interface in my CMake project

117 Views Asked by At

I have a small project which I've refactored into C++ modules. Below is CMakeLists.txt:

cmake_minimum_required(VERSION 3.28)
project(myengine LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
set(PREBUILT_MODULE_PATH ${CMAKE_BINARY_DIR}/engine/modules)

find_package(Vulkan REQUIRED)

if(APPLE)
    add_subdirectory("libs/metal-impl")
endif()

set(ROOT_DIST_DIR ${CMAKE_SOURCE_DIR}/dist)

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    set(DIST_DIR ${ROOT_DIST_DIR}/debug)
else()
    set(DIST_DIR ${ROOT_DIST_DIR}/release)
endif()

file(GLOB_RECURSE MDLS *.ixx)
file(GLOB_RECURSE SRCS *.cxx)

# This is the "object library" target: compiles the sources only once
add_library(objlibmyengine OBJECT)

target_sources(objlibmyengine PUBLIC FILE_SET CXX_MODULES FILES ${MDLS} ${SRCS})

# Shared libraries need PIC
set_property(TARGET objlibmyengine PROPERTY POSITION_INDEPENDENT_CODE 1)

# Shared and static libraries built from the same object files
add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:objlibmyengine>)
add_library(${PROJECT_NAME}static STATIC $<TARGET_OBJECTS:objlibmyengine>)

if (APPLE)

set_target_properties(${PROJECT_NAME} PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY ${DIST_DIR}/lib/macos/
)

endif(APPLE)

include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/engine/src)

if (VULKAN_FOUND)
    message(STATUS "Found Vulkan. Including and linking...")
    target_link_libraries(objlibmyengine Vulkan::Vulkan)
    target_link_libraries(${PROJECT_NAME} Vulkan::Vulkan)
    target_link_libraries(${PROJECT_NAME}static Vulkan::Vulkan)
endif(VULKAN_FOUND)

if(APPLE)
    message(STATUS "Found Metal. Including and linking...")
    target_link_libraries(objlibmyengine metalimpl)
    target_link_libraries(${PROJECT_NAME} metalimpl)
    target_link_libraries(${PROJECT_NAME}static metalimpl)
endif()

What's the hell? Well... Before I had a typical headers+sources C++ project that generated shared and static libraries of my engine (static library for visual engine editor and shared library for the target game compilation). Ok, but why there is an object library? Not to compile sources twice (I didn't find a better solution).

It worked with headers and sources, now I need to achieve a something similar with modules.

Ok, let's dig into the problem... But firstly, I use Clang v16 on macOS v14 (aarch64) and CMake v3.28.3, so it 100% supports C++ modules.

When I try to build the engine it fails with errors:

[main] Building folder: my 
[build] Starting build
[proc] Executing command: /opt/homebrew/bin/cmake --build /Users/denis/Projects/b3/build --config Debug --target all --
[build] [2/11   9% :: 0.043] Scanning /Projects/my/engine/src/my.ixx for CXX dependencies
[build] [2/11  18% :: 0.043] Scanning /Projects/my/engine/src/my.platform.ixx for CXX dependencies
[build] [3/11  27% :: 0.053] Generating CXX dyndep file engine/CMakeFiles/objlibmyengine.dir/CXX.dd
[build] FAILED: engine/CMakeFiles/objlibmyengine.dir/CXX.dd /Projects/my/build/engine/CMakeFiles/objlibmyengine.dir/CXXModules.json engine/CMakeFiles/objlibmyengine.dir/src/my.ixx.o.modmap engine/CMakeFiles/objlibmyengine.dir/src/my.platform.core.ixx.o.modmap engine/CMakeFiles/objlibmyengine.dir/src/my.platform.darwin.ixx.o.modmap engine/CMakeFiles/objlibmyengine.dir/src/my.platform.ixx.o.modmap engine/CMakeFiles/objlibmyengine.dir/src/my.platform.core.cxx.o.modmap engine/CMakeFiles/objlibmyengine.dir/src/my.platform.darwin.cxx.o.modmap 
[build] /opt/homebrew/Cellar/cmake/3.28.3/bin/cmake -E cmake_ninja_dyndep --tdi=engine/CMakeFiles/objlibmyengine.dir/CXXDependInfo.json --lang=CXX --modmapfmt=clang --dd=engine/CMakeFiles/objlibmyengine.dir/CXX.dd @engine/CMakeFiles/objlibmyengine.dir/CXX.dd.rsp
[build] CMake Error: Output engine/CMakeFiles/objlibmyengine.dir/src/my.platform.core.cxx.o is of type `CXX_MODULES` but does not provide a module interface unit or partition
[build] CMake Error: Output engine/CMakeFiles/objlibmyengine.dir/src/my.platform.darwin.cxx.o is of type `CXX_MODULES` but does not provide a module interface unit or partition
[build] ninja: build stopped: subcommand failed.
[proc] The command: /opt/homebrew/bin/cmake --build /Projects/my/build --config Debug --target all -- exited with code: 1
[driver] Build completed: 00:00:00.066

And I can't get why Clang does't see module interfaces. Example of problematic module is below:

my.platform.core.ixx:

export module my.platform.core;

#include <string>

export namespace my 
{

enum class EWindowMode {
    DEFAULT     = 0,
    CUSTOM      = 1,
    MINIMIZED   = 2,
    MAXIMIZED   = 3,
    FULLSCREEN  = 4,
};

struct Rect 
{
    int x;
    int y;
    unsigned int width;
    unsigned int height;

    Rect(int x, int y, unsigned int width, unsigned int height);
};

struct WindowOptions {
    std::string title;
    EWindowMode mode = EWindowMode::DEFAULT;
    bool resizable = true;
    bool closable = true;
    bool minimizable = true;
    bool borderless = false;

    WindowOptions(std::string title);
};

class BaseApp
{
    public:
        BaseApp(std::string name);
        virtual ~BaseApp() {}

        virtual int run() = 0;

        void windowOption(WindowOptions options);

    protected:
        std::string name;
        WindowOptions window_options;
};

}

my.platform.core.cxx:

module;

module my.platform.core;

using namespace my;

Rect::Rect(int x, int y, unsigned int width, unsigned int height):
    x(x), y(y), width(width), height(height)
{
    
}

WindowOptions::WindowOptions(std::string title):
    title(std::move(title))
{
}

BaseApp::BaseApp(std::string name):
    name(std::move(name)),
    window_options(WindowOptions(name))
{
}

void BaseApp::windowOption(WindowOptions options) {
    this->window_options = options;
}

P.S. Modules are a new paradigm in C++ for me, so maybe I declared them wrongly.

1

There are 1 best solutions below

0
Denis Steinman On

Let's resume my problems in that answer if someone will hit into same troubles:

  1. Clang doesn't recognise *.ixx (MS variant) as a module (in case of modules the extension really plays a role, as @Larry has noticed me in comments). So, I've switched to *.cxxm.

  2. I should have used written as below:

     cmake_minimum_required(VERSION 3.28) project(myengine LANGUAGES CXX)
    
     set(CMAKE_CXX_STANDARD 20)
     set(CMAKE_CXX_STANDARD_REQUIRED ON)
     set(CMAKE_CXX_SCAN_FOR_MODULES ON)
     set(PREBUILT_MODULE_PATH ${CMAKE_BINARY_DIR}/engine/modules)
    
     find_package(Vulkan REQUIRED)
    
     if(APPLE)
         add_subdirectory("libs/metal-impl")
     endif()
    
     set(ROOT_DIST_DIR ${CMAKE_SOURCE_DIR}/dist)
    
     if(CMAKE_BUILD_TYPE STREQUAL "Debug")
         set(DIST_DIR ${ROOT_DIST_DIR}/debug) 
     else()
         set(DIST_DIR ${ROOT_DIST_DIR}/release) 
     endif()
    
     file(GLOB_RECURSE MDLS *.cxxm) file(GLOB_RECURSE SRCS *.cxx)
    
     # This is the "object library" target: compiles the sources only once
     add_library(objlibmyengine OBJECT)
    
     # FIX HERE:
     target_sources(objlibmyengine PUBLIC ${SRCS})
     target_sources(objlibmyengine PUBLIC FILE_SET CXX_MODULES FILES ${MDLS})
    
     # Shared libraries need PIC 
     set_property(TARGET objlibmyengine PROPERTY POSITION_INDEPENDENT_CODE 1)
    
     # Shared and static libraries built from the same object files
     add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:objlibmyengine>)
     add_library(${PROJECT_NAME}static STATIC $<TARGET_OBJECTS:objlibmyengine>)
    
     if (APPLE)
    
         set_target_properties(${PROJECT_NAME} PROPERTIES
     LIBRARY_OUTPUT_DIRECTORY ${DIST_DIR}/lib/macos/ )
    
     endif(APPLE)
    
     include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/engine/src)
    
     if (VULKAN_FOUND)
         message(STATUS "Found Vulkan. Including and linking...")
         target_link_libraries(objlibmyengine Vulkan::Vulkan)
         target_link_libraries(${PROJECT_NAME} Vulkan::Vulkan)
         target_link_libraries(${PROJECT_NAME}static Vulkan::Vulkan)
     endif(VULKAN_FOUND)
    
     if(APPLE)
         message(STATUS "Found Metal. Including and linking...")
         target_link_libraries(objlibmyengine metalimpl)
         target_link_libraries(${PROJECT_NAME} metalimpl)
         target_link_libraries(${PROJECT_NAME}static metalimpl) 
     endif()
    

After renaming module files to *.cxxm and fix CMakeLists.txt Clang could build the library BUT I faced new problems:

  1. VS Code has a bad syntax highlighting for modules, at least in my case it didn't work properly. I think it's possible to resolve but you need to dig into VS Code settings.
  2. My root CMakeLists.txt contained set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") and Clang complained that it can't build C++ modules for both architectures. And it has a workaround too, for example, I can write a shell script to run my CMake twice with different architecture flags or something else. However I give up!

A little off topic: All these problems and a ton of tutorials let me understand that C++ modules are still too young technology and its support is not so well still. I've decided to revert my lib back to traditional sources and headers. Maybe later I will try switching to C++ modules again when a module support will be more mature.