cmake_minimum_required(VERSION 3.30) # --- C++26 modules + `import std;` (clang + libc++) ------------------------- # CMake gates `import std` behind an experimental value documented in # Help/dev/experimental.rst for the CMake version in use. set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444") # Path to the standard-library module description. A cross toolchain overrides # this before the compiler is probed; this default targets the Linux LLVM # package. Skipped for Emscripten, whose toolchain supplies its own sysroot # modules.json. if(NOT CMAKE_CXX_STDLIB_MODULES_JSON AND NOT DEFINED ENV{EMSDK}) set(CMAKE_CXX_STDLIB_MODULES_JSON "/usr/lib/llvm-21/lib/libc++.modules.json") endif() # libc++ must be selected before the compiler is probed: CMake builds its # internal standard-library module target during `project()`, and that target # only sees `CMAKE_CXX_FLAGS` (not later `add_compile_options`). The standard # and extension settings must also be in place before `project()` so the # internal `std.pcm` is built with the same configuration as our modules. set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++") set(CMAKE_EXE_LINKER_FLAGS_INIT "-stdlib=libc++") set(CMAKE_SHARED_LINKER_FLAGS_INIT "-stdlib=libc++") set(CMAKE_CXX_STANDARD 26) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_SCAN_FOR_MODULES ON) project(OpenRA3 VERSION 0.6.0 LANGUAGES C CXX) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") option(OPENRA3_WERROR "Treat warnings as errors" OFF) # Windows: statically link the C++ runtime (libc++, libunwind) so the .exe only # needs SDL3.dll next to it at runtime. if(WIN32) add_link_options(-static) endif() function(openra3_target_defaults target) target_compile_options(${target} PRIVATE -Wall -Wextra) set_property(TARGET ${target} PROPERTY CXX_MODULE_STD ON) if(OPENRA3_WERROR) target_compile_options(${target} PRIVATE -Werror) endif() endfunction() # --- SDL3: pkg-config on Linux, or an explicit MinGW root for Windows -------- add_library(openra3_sdl3 INTERFACE) set(OPENRA3_HAS_SDL3 OFF) # The triple-named subdirectory inside a MinGW SDL3 development package # (`//{include,lib,bin}`); x86_64 unless a toolchain overrides it. set(OPENRA3_SDL3_MINGW_TRIPLE "x86_64-w64-mingw32" CACHE STRING "SDL3 MinGW triple subdirectory") if(EMSCRIPTEN) # The wasm build runs the engine on a Web Worker and renders into an # OffscreenCanvas (ra3.webgpu / ra3.wasmgl), so it links no SDL: SDL3's # Emscripten port is main-thread DOM only and cannot run in a worker. elseif(OPENRA3_SDL3_ROOT) target_include_directories(openra3_sdl3 INTERFACE "${OPENRA3_SDL3_ROOT}/${OPENRA3_SDL3_MINGW_TRIPLE}/include") target_link_libraries(openra3_sdl3 INTERFACE "${OPENRA3_SDL3_ROOT}/${OPENRA3_SDL3_MINGW_TRIPLE}/lib/libSDL3.dll.a") set(OPENRA3_HAS_SDL3 ON) else() find_package(PkgConfig QUIET) if(PkgConfig_FOUND) pkg_check_modules(SDL3 QUIET IMPORTED_TARGET sdl3) endif() if(SDL3_FOUND) target_link_libraries(openra3_sdl3 INTERFACE PkgConfig::SDL3) set(OPENRA3_HAS_SDL3 ON) endif() endif() if(OPENRA3_HAS_SDL3) message(STATUS "OpenRA3: SDL3 found - windowed viewer enabled") else() message(STATUS "OpenRA3: SDL3 not found - offscreen BMP output only") endif() # --- Vulkan: vendored volk + headers, so no Vulkan SDK is needed --------------- # The loader is resolved at runtime (volk dlopen/LoadLibrary), which lets the # same source build the Vulkan backend on Linux and on the MinGW Windows target. # WebAssembly has no Vulkan, so the viewer is off there (the null backend). if(EMSCRIPTEN) set(OPENRA3_VULKAN_DEFAULT OFF) else() set(OPENRA3_VULKAN_DEFAULT ON) endif() option(OPENRA3_VULKAN "Build the Vulkan viewer" ${OPENRA3_VULKAN_DEFAULT}) set(OPENRA3_HAS_VULKAN OFF) if(OPENRA3_VULKAN AND OPENRA3_HAS_SDL3 AND NOT EMSCRIPTEN) set(OPENRA3_HAS_VULKAN ON) endif() if(OPENRA3_HAS_VULKAN) message(STATUS "OpenRA3: Vulkan viewer enabled (vendored volk)") else() message(STATUS "OpenRA3: Vulkan viewer disabled - offscreen image only") endif() if(NOT EMSCRIPTEN) add_library(ra3_volk STATIC third_party/volk/volk.c) target_include_directories(ra3_volk PUBLIC third_party/volk third_party/vulkan/include) target_compile_definitions(ra3_volk PUBLIC VK_NO_PROTOTYPES) endif() # Embed the committed SPIR-V blobs into a generated header at configure time. # Regenerate with shaders/compile.sh after editing a .vert/.frag. function(openra3_embed_spirv out_header) set(content "// Generated by CMake from shaders/generated/*.spv.\n") string(APPEND content "#pragma once\n#include \n#include \n\nnamespace ra3_shaders {\n") foreach(spv IN LISTS ARGN) if(NOT EXISTS "${spv}") message(FATAL_ERROR "Missing precompiled shader ${spv}; run shaders/compile.sh") endif() get_filename_component(base "${spv}" NAME) string(MAKE_C_IDENTIFIER "${base}" ident) file(READ "${spv}" hex HEX) string(LENGTH "${hex}" hexlen) math(EXPR words "${hexlen} / 8") string(REGEX REPLACE "(..)(..)(..)(..)" "0x\\4\\3\\2\\1," words_list "${hex}") string(APPEND content "inline constexpr std::uint32_t ${ident}[] = {${words_list}};\n") string(APPEND content "inline constexpr std::size_t ${ident}_words = ${words};\n\n") endforeach() string(APPEND content "} // namespace ra3_shaders\n") file(WRITE "${out_header}" "${content}") endfunction() # Embed the HLSL sources as string literals. The Direct3D backends compile them # at runtime with d3dcompiler_47, so no HLSL compiler is needed at build time # (the Windows target cross-compiles from Linux). function(openra3_embed_hlsl out_header) set(content "// Generated by CMake from shaders/*.hlsl.\n") string(APPEND content "#pragma once\n\nnamespace ra3_shaders {\n") foreach(src IN LISTS ARGN) if(NOT EXISTS "${src}") message(FATAL_ERROR "Missing HLSL shader ${src}") endif() get_filename_component(base "${src}" NAME_WE) string(MAKE_C_IDENTIFIER "${base}" ident) string(APPEND ident "_hlsl") file(READ "${src}" text) string(APPEND content "inline constexpr const char ${ident}[] = R\"RA3HLSL(${text})RA3HLSL\";\n\n") endforeach() string(APPEND content "} // namespace ra3_shaders\n") file(WRITE "${out_header}" "${content}") endfunction() # Embed the GLSL ES sources for the WebGL backend as string literals (compiled # at runtime by the browser's GL, like the HLSL for Direct3D). function(openra3_embed_glsl out_header) set(content "// Generated by CMake from shaders/*.glsl.\n") string(APPEND content "#pragma once\n\nnamespace ra3_shaders {\n") foreach(src IN LISTS ARGN) if(NOT EXISTS "${src}") message(FATAL_ERROR "Missing GLSL shader ${src}") endif() get_filename_component(base "${src}" NAME_WE) string(MAKE_C_IDENTIFIER "${base}" ident) string(APPEND ident "_glsl") file(READ "${src}" text) string(APPEND content "inline constexpr const char ${ident}[] = R\"RA3GLSL(${text})RA3GLSL\";\n\n") endforeach() string(APPEND content "} // namespace ra3_shaders\n") file(WRITE "${out_header}" "${content}") endfunction() # Embed the WGSL sources for the WebGPU backend as string literals (compiled at # runtime by the browser's WebGPU, like the GLSL/HLSL for the other backends). function(openra3_embed_wgsl out_header) set(content "// Generated by CMake from shaders/*.wgsl.\n") string(APPEND content "#pragma once\n\nnamespace ra3_shaders {\n") foreach(src IN LISTS ARGN) if(NOT EXISTS "${src}") message(FATAL_ERROR "Missing WGSL shader ${src}") endif() get_filename_component(base "${src}" NAME_WE) string(MAKE_C_IDENTIFIER "${base}" ident) string(APPEND ident "_wgsl") file(READ "${src}" text) string(APPEND content "inline constexpr const char ${ident}[] = R\"RA3WGSL(${text})RA3WGSL\";\n\n") endforeach() string(APPEND content "} // namespace ra3_shaders\n") file(WRITE "${out_header}" "${content}") endfunction() # --- logging (vendored libenderlog, MIT) ------------------------------------- # A standalone C++26 module logger (`import ender.log;`) with a per-run # archiving file sink. libc++ has no , so on this toolchain the # module records call sites but no stacks; it defaults ENDERLOG_HAS_STACKTRACE # to 0 without any define. add_library(ra3_enderlog STATIC) target_sources(ra3_enderlog PUBLIC FILE_SET CXX_MODULES FILES third_party/libenderlog/src/ender.log.cppm) target_compile_features(ra3_enderlog PUBLIC cxx_std_26) openra3_target_defaults(ra3_enderlog) # --- engine core ------------------------------------------------------------- add_library(ra3_core STATIC) target_sources(ra3_core PUBLIC FILE_SET CXX_MODULES FILES src/core/ra3.core.cppm) openra3_target_defaults(ra3_core) # --- game logic -------------------------------------------------------------- add_library(ra3_logic STATIC) target_sources(ra3_logic PUBLIC FILE_SET CXX_MODULES FILES src/logic/ra3.logic.cppm) target_link_libraries(ra3_logic PUBLIC ra3_core) openra3_target_defaults(ra3_logic) # --- RA3 gameplay data (real balance) ---------------------------------------- add_library(ra3_data STATIC) target_sources(ra3_data PUBLIC FILE_SET CXX_MODULES FILES src/data/ra3.data.cppm) target_link_libraries(ra3_data PUBLIC ra3_core) openra3_target_defaults(ra3_data) # --- client ------------------------------------------------------------------ add_library(ra3_client STATIC) target_sources(ra3_client PUBLIC FILE_SET CXX_MODULES FILES src/client/ra3.client.cppm) target_link_libraries(ra3_client PUBLIC ra3_core ra3_logic) openra3_target_defaults(ra3_client) # --- RA3 game definitions ---------------------------------------------------- add_library(ra3_game STATIC) target_sources(ra3_game PUBLIC FILE_SET CXX_MODULES FILES src/game/ra3.game.cppm) target_link_libraries(ra3_game PUBLIC ra3_core ra3_logic) openra3_target_defaults(ra3_game) # --- filesystem: BIG4 + RefPack --------------------------------------------- add_library(ra3_fs STATIC) target_sources(ra3_fs PUBLIC FILE_SET CXX_MODULES FILES src/fs/ra3.fs.cppm) target_link_libraries(ra3_fs PUBLIC ra3_core) openra3_target_defaults(ra3_fs) # --- map discovery/loading --------------------------------------------------- add_library(ra3_map STATIC) target_sources(ra3_map PUBLIC FILE_SET CXX_MODULES FILES src/map/ra3.map.cppm) target_link_libraries(ra3_map PUBLIC ra3_core ra3_fs) openra3_target_defaults(ra3_map) # --- minimal skirmish simulation -------------------------------------------- add_library(ra3_skirmish STATIC) target_sources(ra3_skirmish PUBLIC FILE_SET CXX_MODULES FILES src/skirmish/ra3.skirmish.cppm) target_link_libraries(ra3_skirmish PUBLIC ra3_core ra3_logic ra3_game ra3_data ra3_map) openra3_target_defaults(ra3_skirmish) # --- software renderer ------------------------------------------------------- add_library(ra3_render STATIC) target_sources(ra3_render PUBLIC FILE_SET CXX_MODULES FILES src/render/ra3.render.cppm) target_link_libraries(ra3_render PUBLIC ra3_core) openra3_target_defaults(ra3_render) # --- real map terrain (HeightMapData / BlendTileData) ------------------------ add_library(ra3_terrain STATIC) target_sources(ra3_terrain PUBLIC FILE_SET CXX_MODULES FILES src/terrain/ra3.terrain.cppm) target_link_libraries(ra3_terrain PUBLIC ra3_core ra3_fs ra3_render) openra3_target_defaults(ra3_terrain) # The presentation facade imports render + terrain (for the terrain capability). target_link_libraries(ra3_client PUBLIC ra3_render ra3_terrain) # --- windowed viewer (SDL3 or null) ----------------------------------------- add_library(ra3_ui STATIC) if(OPENRA3_HAS_SDL3) target_sources(ra3_ui PUBLIC FILE_SET CXX_MODULES FILES src/ui/ra3.ui.sdl.cppm) target_link_libraries(ra3_ui PUBLIC ra3_core ra3_render ra3_client openra3_sdl3) else() target_sources(ra3_ui PUBLIC FILE_SET CXX_MODULES FILES src/ui/ra3.ui.null.cppm) target_link_libraries(ra3_ui PUBLIC ra3_core ra3_render ra3_client) endif() openra3_target_defaults(ra3_ui) # --- Vulkan viewer (SDL3 surface + Vulkan, or null fallback) ------------------ add_library(ra3_vulkan STATIC) if(OPENRA3_HAS_VULKAN AND OPENRA3_HAS_SDL3) openra3_embed_spirv( "${CMAKE_CURRENT_BINARY_DIR}/generated/shaders_embedded.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/scene.vert.spv" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/scene.frag.spv" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/terrain.vert.spv" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/terrain.frag.spv" ) # The blobs are read at configure time, so re-run CMake when they change. set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/scene.vert.spv" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/scene.frag.spv" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/terrain.vert.spv" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/generated/terrain.frag.spv") target_sources(ra3_vulkan PUBLIC FILE_SET CXX_MODULES FILES src/vulkan/ra3.vulkan.sdl.cppm) target_include_directories(ra3_vulkan PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated") target_link_libraries(ra3_vulkan PUBLIC ra3_core ra3_render ra3_terrain ra3_client openra3_sdl3 ra3_volk) else() target_sources(ra3_vulkan PUBLIC FILE_SET CXX_MODULES FILES src/vulkan/ra3.vulkan.null.cppm) target_link_libraries(ra3_vulkan PUBLIC ra3_core ra3_render ra3_client) endif() openra3_target_defaults(ra3_vulkan) # --- Direct3D viewer (D3D11 + D3D12, or null fallback) ------------------------ # Direct3D is Windows-only, so the real backends build only for the MinGW target; # every other platform links the `ra3.dx.null` fallback that fails `init` cleanly. option(OPENRA3_DX "Build the Direct3D 11/12 viewer" ON) set(OPENRA3_HAS_DX OFF) if(OPENRA3_DX AND WIN32 AND OPENRA3_HAS_SDL3) set(OPENRA3_HAS_DX ON) endif() if(OPENRA3_HAS_DX) message(STATUS "OpenRA3: Direct3D 11/12 viewer enabled (runtime HLSL via d3dcompiler)") else() message(STATUS "OpenRA3: Direct3D viewer disabled - offscreen image only") endif() add_library(ra3_dx STATIC) if(OPENRA3_HAS_DX) openra3_embed_hlsl( "${CMAKE_CURRENT_BINARY_DIR}/generated/dx_shaders_embedded.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/dx_scene.hlsl" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/dx_terrain.hlsl" ) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/shaders/dx_scene.hlsl" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/dx_terrain.hlsl") target_sources(ra3_dx PUBLIC FILE_SET CXX_MODULES FILES src/dx/ra3.dx.cppm) target_include_directories(ra3_dx PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated") target_link_libraries(ra3_dx PUBLIC ra3_core ra3_render ra3_terrain ra3_client ra3_enderlog openra3_sdl3 d3d11 d3d12 dxgi d3dcompiler) else() target_sources(ra3_dx PUBLIC FILE_SET CXX_MODULES FILES src/dx/ra3.dx.null.cppm) target_link_libraries(ra3_dx PUBLIC ra3_core ra3_render ra3_client) endif() openra3_target_defaults(ra3_dx) # --- wasm worker viewer (SDL-free OffscreenCanvas + WebGL2, or null fallback) -- # The default wasm backend: the engine runs on a plain Web Worker and renders # into an OffscreenCanvas transferred from the page, with no SDL. Non-wasm builds # link the null stub (init fails, so desktop backends are unaffected). option(OPENRA3_WASMGL "Build the SDL-free wasm worker viewer (Emscripten)" ON) set(OPENRA3_HAS_WASMGL OFF) if(OPENRA3_WASMGL AND EMSCRIPTEN) set(OPENRA3_HAS_WASMGL ON) endif() add_library(ra3_wasmgl STATIC) if(OPENRA3_HAS_WASMGL) message(STATUS "OpenRA3: SDL-free wasm worker viewer enabled") openra3_embed_glsl( "${CMAKE_CURRENT_BINARY_DIR}/generated/wasmgl_glsl_embedded.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_scene_vert.glsl" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_scene_frag.glsl" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_terrain_vert.glsl" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_terrain_frag.glsl" ) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_scene_vert.glsl" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_scene_frag.glsl" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_terrain_vert.glsl" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgl_terrain_frag.glsl") target_sources(ra3_wasmgl PUBLIC FILE_SET CXX_MODULES FILES src/wasmgl/ra3.wasmgl.cppm) target_include_directories(ra3_wasmgl PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated") target_link_libraries(ra3_wasmgl PUBLIC ra3_core ra3_render ra3_terrain ra3_client ra3_enderlog) # Target WebGL2 so GLES3-only entry points (glTexImage3D, VAOs, ...) link. target_link_options(ra3_wasmgl PUBLIC -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2) else() message(STATUS "OpenRA3: SDL-free wasm worker viewer disabled (non-wasm) - null backend") target_sources(ra3_wasmgl PUBLIC FILE_SET CXX_MODULES FILES src/wasmgl/ra3.wasmgl.null.cppm) target_link_libraries(ra3_wasmgl PUBLIC ra3_core ra3_render ra3_client) endif() openra3_target_defaults(ra3_wasmgl) # --- wasm WebGPU viewer (Dawn emdawnwebgpu, or null fallback) ------------------ # A peer of ra3.wasmgl in the same Web Worker; web WebGPU is WGSL-only, so it has # its own WGSL shaders (the Vulkan SPIR-V blobs cannot be used on the web). The # `--use-port=emdawnwebgpu` port is downloaded/cached by Emscripten and linked # into the final module. option(OPENRA3_WEBGPU "Build the WebGPU wasm viewer (Emscripten emdawnwebgpu)" ON) set(OPENRA3_HAS_WEBGPU OFF) if(OPENRA3_WEBGPU AND EMSCRIPTEN) set(OPENRA3_HAS_WEBGPU ON) endif() add_library(ra3_webgpu STATIC) if(OPENRA3_HAS_WEBGPU) message(STATUS "OpenRA3: WebGPU wasm viewer enabled (emdawnwebgpu)") openra3_embed_wgsl( "${CMAKE_CURRENT_BINARY_DIR}/generated/webgpu_wgsl_embedded.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgpu_scene.wgsl" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgpu_terrain.wgsl" ) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgpu_scene.wgsl" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/webgpu_terrain.wgsl") target_sources(ra3_webgpu PUBLIC FILE_SET CXX_MODULES FILES src/webgpu/ra3.webgpu.cppm) target_include_directories(ra3_webgpu PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated") # CMake's module dependency scanner does not process `--use-port`, so point # the include path at the (image-baked) port package explicitly. set(OPENRA3_EMDAWNWEBGPU_DIR "$ENV{EMSDK}/upstream/emscripten/cache/ports/emdawnwebgpu/emdawnwebgpu_pkg") target_include_directories(ra3_webgpu SYSTEM PRIVATE "${OPENRA3_EMDAWNWEBGPU_DIR}/webgpu_cpp/include" "${OPENRA3_EMDAWNWEBGPU_DIR}/webgpu/include") target_compile_options(ra3_webgpu PUBLIC --use-port=emdawnwebgpu) target_link_options(ra3_webgpu PUBLIC --use-port=emdawnwebgpu) target_link_libraries(ra3_webgpu PUBLIC ra3_core ra3_render ra3_terrain ra3_client ra3_wasmgl ra3_enderlog) else() message(STATUS "OpenRA3: WebGPU wasm viewer disabled (non-wasm) - null backend") target_sources(ra3_webgpu PUBLIC FILE_SET CXX_MODULES FILES src/webgpu/ra3.webgpu.null.cppm) target_link_libraries(ra3_webgpu PUBLIC ra3_core ra3_render ra3_client) endif() openra3_target_defaults(ra3_webgpu) # --- display backend selection ------------------------------------------------ add_library(ra3_display STATIC) target_sources(ra3_display PUBLIC FILE_SET CXX_MODULES FILES src/display/ra3.display.cppm) target_link_libraries(ra3_display PUBLIC ra3_core ra3_render ra3_terrain ra3_client ra3_ui ra3_vulkan ra3_dx ra3_wasmgl ra3_webgpu) openra3_target_defaults(ra3_display) # --- umbrella ----------------------------------------------------------------- add_library(ra3 STATIC) target_sources(ra3 PUBLIC FILE_SET CXX_MODULES FILES src/ra3.cppm) target_link_libraries(ra3 PUBLIC ra3_core ra3_logic ra3_client ra3_data ra3_game ra3_fs ra3_map ra3_skirmish ra3_render ra3_terrain ra3_display ra3_ui ra3_vulkan ra3_dx ra3_wasmgl ra3_webgpu ra3_enderlog) openra3_target_defaults(ra3) # --- executable --------------------------------------------------------------- add_executable(openra3 apps/openra3/main.cpp) target_link_libraries(openra3 PRIVATE ra3) openra3_target_defaults(openra3) if(WIN32 AND OPENRA3_SDL3_ROOT) add_custom_command(TARGET openra3 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${OPENRA3_SDL3_ROOT}/${OPENRA3_SDL3_MINGW_TRIPLE}/bin/SDL3.dll" "$") endif() if(EMSCRIPTEN) # Emit openra3.js/.wasm. The engine runs inside apps/web/openra3.worker.js # (a plain Web Worker), which the page (apps/web/index.html) loads and hands # an OffscreenCanvas; so there is no Emscripten HTML shell here - both web # files are copied next to the module. set_target_properties(openra3 PROPERTIES SUFFIX ".js") target_link_options(openra3 PRIVATE -sFORCE_FILESYSTEM=1 # index.html input glue calls the exported openra3_* functions via ccall. "-sEXPORTED_RUNTIME_METHODS=ccall,cwrap") add_custom_command(TARGET openra3 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/apps/web/index.html" "${CMAKE_CURRENT_SOURCE_DIR}/apps/web/openra3.worker.js" "$" COMMENT "Copying the OpenRA3 web page and worker bootstrap") # On-demand assets: `OPENRA3_WEB_MANIFEST` is a JSON `{"files": [...]}` listing # every asset relative to the assets root (generate it with # scripts/make_web_manifest.py). Only that small manifest is preloaded, to # /assets.manifest.json; at startup the worker registers every listed file as # an Emscripten lazy file, so the browser fetches each asset when the engine # first opens it (serve the tree at /assets, or pass serve.py --assets). set(OPENRA3_WEB_MANIFEST "" CACHE FILEPATH "Assets manifest JSON, embedded as /assets.manifest.json for lazy loading") if(OPENRA3_WEB_MANIFEST AND EXISTS "${OPENRA3_WEB_MANIFEST}") target_link_options(openra3 PRIVATE "--embed-file=${OPENRA3_WEB_MANIFEST}@/assets.manifest.json") endif() # Legacy: preload a whole assets directory into the virtual FS at /assets (the # monolithic path the lazy worker backend replaces). Only used when no manifest # is given, since lazy files cannot shadow a preloaded tree. set(OPENRA3_WEB_ASSETS "" CACHE PATH "Assets directory preloaded into the wasm FS at /assets (legacy)") if(NOT (OPENRA3_WEB_MANIFEST AND EXISTS "${OPENRA3_WEB_MANIFEST}") AND OPENRA3_WEB_ASSETS AND EXISTS "${OPENRA3_WEB_ASSETS}") target_link_options(openra3 PRIVATE "--preload-file=${OPENRA3_WEB_ASSETS}@/assets") endif() endif() # --- packaging (.deb / .msi / portable archives) ----------------------------- # CPack produces the release artifacts in one shot: # Linux -> DEB (dependencies derived from the binary with dpkg-shlibdeps) # plus a portable .tar.gz # Windows -> a Windows Installer .msi (built with wixl from msitools when the # WiX tools are absent, so the Linux cross image can make it) plus a # portable .zip with the exe and SDL3.dll # OPENRA3_DISTRO_TAG (e.g. ubuntu26.04) is appended to the Linux file name so the # per-distro images do not collide. install(TARGETS openra3 RUNTIME DESTINATION bin) if(WIN32 AND OPENRA3_SDL3_ROOT) install(FILES "${OPENRA3_SDL3_ROOT}/${OPENRA3_SDL3_MINGW_TRIPLE}/bin/SDL3.dll" DESTINATION bin) endif() set(OPENRA3_DISTRO_TAG "" CACHE STRING "Distro tag appended to package file names (e.g. ubuntu26.04)") set(CPACK_PACKAGE_NAME "openra3") set(CPACK_PACKAGE_VENDOR "OpenRA3") set(CPACK_PACKAGE_CONTACT "OpenRA3 ") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "OpenRA3 - a from-scratch reimplementation of Red Alert 3") set(CPACK_PACKAGE_HOMEPAGE_URL "https://git.ender.cool/EnderTheCoder/openra3") set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") set(CPACK_PACKAGE_EXECUTABLES "openra3" "OpenRA3") set(CPACK_STRIP_FILES ON) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") endif() if(WIN32) if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") set(OPENRA3_WIN_ARCH "arm64") else() set(OPENRA3_WIN_ARCH "x86_64") endif() set(CPACK_PACKAGE_FILE_NAME "openra3-${PROJECT_VERSION}-windows-${OPENRA3_WIN_ARCH}") # Portable .zip (exe + SDL3.dll). set(CPACK_GENERATOR "ZIP") # A Windows Installer (.msi) straight from the Linux cross build via wixl # (msitools). wixl 0.106 has no arm64 support, so the MSI is offered for # x86_64 and ARM64 ships the portable zip. find_program(OPENRA3_WIXL wixl) if(OPENRA3_WIXL AND NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/packaging/openra3.wxs.in" "${CMAKE_CURRENT_BINARY_DIR}/openra3.wxs" @ONLY) add_custom_target(openra3_msi COMMAND "${OPENRA3_WIXL}" --arch x64 -D "exe=$" -D "dll=${OPENRA3_SDL3_ROOT}/${OPENRA3_SDL3_MINGW_TRIPLE}/bin/SDL3.dll" -o "$/${CPACK_PACKAGE_FILE_NAME}.msi" "${CMAKE_CURRENT_BINARY_DIR}/openra3.wxs" DEPENDS openra3 COMMENT "Building Windows Installer (${CPACK_PACKAGE_FILE_NAME}.msi) with wixl" VERBATIM) endif() else() if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "arm64") else() set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64") endif() set(CPACK_PACKAGE_FILE_NAME "openra3_${PROJECT_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}${OPENRA3_DISTRO_TAG}") set(CPACK_GENERATOR "TGZ;DEB") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_DEBIAN_PACKAGE_SECTION "games") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") endif() include(CPack) # --- extracted assets --------------------------------------------------------- # At build time, extract the retail assets into /assets so the # executable is self-contained (no --game-dir at run time). This drives the # ra3tools Python scripts (the game install + Python, no target executable), so # it also runs for a cross-build when the install is mounted into the container # and RA3_GAME_DIR points at it. The engine's own map/terrain reader is embedded # in the exe (run `openra3 extract` on a native host if you prefer). set(RA3_GAME_DIR "C:/Red Alert 3" CACHE PATH "Red Alert 3 installation used to extract assets") set(RA3TOOLS_DIR "C:/Users/Ender/CLionProjects/ra3-headless/ra3tools" CACHE PATH "ra3tools scripts (asset extraction)") find_program(OPENRA3_PYTHON NAMES python3 python) set(PYTHON_EXECUTABLE "${OPENRA3_PYTHON}" CACHE FILEPATH "Python interpreter used to run ra3tools") option(OPENRA3_EXTRACT_ALL "Extract every asset (models/textures/audio/movies) via ra3tools" ON) if(EXISTS "${RA3_GAME_DIR}/Data" AND OPENRA3_PYTHON) add_custom_command( OUTPUT "${CMAKE_BINARY_DIR}/.assets_stamp" COMMAND ${CMAKE_COMMAND} "-DGAME_DIR=${RA3_GAME_DIR}" "-DOUT=$/assets" "-DPYTHON=${PYTHON_EXECUTABLE}" "-DRA3TOOLS=${RA3TOOLS_DIR}" "-DEXTRACT_ALL=$,ON,OFF>" "-DSTAMP=${CMAKE_BINARY_DIR}/.assets_stamp" -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/extract_assets.cmake" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/extract_assets.cmake" COMMENT "Extracting Red Alert 3 assets to $/assets" VERBATIM) add_custom_target(openra3_assets ALL DEPENDS "${CMAKE_BINARY_DIR}/.assets_stamp") else() message(STATUS "OpenRA3: asset extraction skipped (RA3_GAME_DIR '${RA3_GAME_DIR}' or Python not found)") endif() # --- tests -------------------------------------------------------------------- enable_testing() add_executable(ra3_tests tests/ra3_tests.cpp) target_link_libraries(ra3_tests PRIVATE ra3) openra3_target_defaults(ra3_tests) add_test(NAME ra3_tests COMMAND ra3_tests)