我有一个小项目,我已将其重构为 C++ 模块。以下是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()
到底是什么?嗯...之前我有一个典型的 headers+sources C++ 项目,它生成我的引擎的共享库和静态库(用于视觉引擎编辑器的静态库和用于目标游戏编译的共享库)。好的,但是为什么要有对象库呢?不要编译两次源码(我没有找到更好的解决方案)。
它适用于标头和源代码,现在我需要通过模块实现类似的功能。
好吧,让我们深入研究这个问题...但首先,我在 macOS v14 (aarch64) 和 CMake v3.28.3 上使用 Clang v16,因此它 100% 支持 C++ 模块。
当我尝试构建引擎时,它失败并出现错误:
[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
我不明白为什么 Clang 看不到模块接口。有问题的模块示例如下:
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. 模块对我来说是 C++ 中的一个新范例,所以也许我错误地声明了它们。
如果有人遇到同样的麻烦,让我们在该答案中继续我的问题:
Clang 无法将
*.ixx
(MS 变体)识别为模块(在模块的情况下,扩展确实发挥了作用,正如@Larry 在评论中注意到我一样)。所以,我已经切换到*.cxxm
。
我应该使用如下写法:
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()
将模块文件重命名为
*.cxxm
并修复 CMakeLists.txt
Clang 可以构建库,但我遇到了新问题:
CMakeLists.txt
包含set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
并且Clang抱怨它无法为两种架构构建C++模块。它也有一个解决方法,例如,我可以编写一个 shell 脚本来使用不同的架构标志或其他东西运行我的 CMake 两次。有点题外话:所有这些问题和大量的教程让我明白C++模块仍然是一个太年轻的技术,它的支持还不是很好。我决定将我的库恢复为传统的源代码和标头。也许稍后当模块支持更加成熟时我会再次尝试切换到 C++ 模块。