From f43b364707ab0ceaea78f86334bb7fadf0cfdd3b Mon Sep 17 00:00:00 2001 From: kindem Date: Sat, 20 Jun 2026 18:23:28 +0800 Subject: [PATCH 1/8] feat: add a simple file template engine to Common Add Common::TemplateEngine for {{ }} variable substitution over strings and files, with Result-based error reporting (undefined/unterminated variables and file IO surface as Err instead of asserting). Convert the Default project template's CMakeLists.txt to a .tpl using the new syntax. --- .../ProjectTemplates/Default/CMakeLists.txt | 4 - .../Default/CMakeLists.txt.tpl | 4 + .../Source/Common/Include/Common/Template.h | 28 +++++ Engine/Source/Common/Src/Template.cpp | 114 ++++++++++++++++++ Engine/Source/Common/Test/TemplateTest.cpp | 101 ++++++++++++++++ 5 files changed, 247 insertions(+), 4 deletions(-) delete mode 100644 Editor/Resource/ProjectTemplates/Default/CMakeLists.txt create mode 100644 Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl create mode 100644 Engine/Source/Common/Include/Common/Template.h create mode 100644 Engine/Source/Common/Src/Template.cpp create mode 100644 Engine/Source/Common/Test/TemplateTest.cpp diff --git a/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt b/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt deleted file mode 100644 index 99a143ec..00000000 --- a/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -cmake_minimum_required(VERSION %{cmakeMinVersion}) -project(%{projectName}%) - -%{findEngineCMakeScripts}% diff --git a/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl b/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl new file mode 100644 index 00000000..094d4407 --- /dev/null +++ b/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION {{cmakeMinVersion}}) +project({{projectName}}) + +{{findEngineCMakeScripts}} diff --git a/Engine/Source/Common/Include/Common/Template.h b/Engine/Source/Common/Include/Common/Template.h new file mode 100644 index 00000000..a25a8469 --- /dev/null +++ b/Engine/Source/Common/Include/Common/Template.h @@ -0,0 +1,28 @@ +// +// Created by johnk on 2026/6/20. +// + +#pragma once + +#include +#include + +#include + +namespace Common { + class TemplateEngine { + public: + TemplateEngine& Set(const std::string& inKey, const std::string& inValue); + TemplateEngine& Set(const std::unordered_map& inVariables); + + bool Has(const std::string& inKey) const; + void Clear(); + + Result Render(const std::string& inText) const; + Result RenderFile(const std::string& inSrcFile) const; + Result RenderFileTo(const std::string& inSrcFile, const std::string& inDstFile) const; + + private: + std::unordered_map variables; + }; +} diff --git a/Engine/Source/Common/Src/Template.cpp b/Engine/Source/Common/Src/Template.cpp new file mode 100644 index 00000000..3914d666 --- /dev/null +++ b/Engine/Source/Common/Src/Template.cpp @@ -0,0 +1,114 @@ +// +// Created by johnk on 2026/6/20. +// + +#include +#include +#include + +#include +#include + +namespace Common::Internal { + constexpr std::string_view openTag = "{{"; + constexpr std::string_view closeTag = "}}"; + + static std::string Trim(const std::string& inStr) + { + constexpr std::string_view whitespace = " \t\r\n"; + const size_t begin = inStr.find_first_not_of(whitespace); + if (begin == std::string::npos) { + return {}; + } + const size_t end = inStr.find_last_not_of(whitespace); + return inStr.substr(begin, end - begin + 1); + } + + static std::string FormatUnresolved(const std::vector& inKeys) + { + std::string joined; + for (size_t i = 0; i < inKeys.size(); i++) { + joined += std::format("'{}'", inKeys[i]); + if (i + 1 < inKeys.size()) { + joined += ", "; + } + } + return std::format("template has unresolved variable(s): {}", joined); + } +} + +namespace Common { + TemplateEngine& TemplateEngine::Set(const std::string& inKey, const std::string& inValue) + { + variables[inKey] = inValue; + return *this; + } + + TemplateEngine& TemplateEngine::Set(const std::unordered_map& inVariables) + { + for (const auto& [key, value] : inVariables) { + variables[key] = value; + } + return *this; + } + + bool TemplateEngine::Has(const std::string& inKey) const + { + return variables.contains(inKey); + } + + void TemplateEngine::Clear() + { + variables.clear(); + } + + Result TemplateEngine::Render(const std::string& inText) const + { + std::string result; + result.reserve(inText.size()); + std::vector unresolved; + + size_t cursor = 0; + while (cursor < inText.size()) { + const size_t open = inText.find(Internal::openTag, cursor); + if (open == std::string::npos) { + result.append(inText, cursor, std::string::npos); + break; + } + result.append(inText, cursor, open - cursor); + + const size_t keyStart = open + Internal::openTag.size(); + const size_t close = inText.find(Internal::closeTag, keyStart); + if (close == std::string::npos) { + unresolved.emplace_back(Internal::Trim(inText.substr(keyStart))); + break; + } + + const std::string key = Internal::Trim(inText.substr(keyStart, close - keyStart)); + if (const auto it = variables.find(key); + it != variables.end()) { + result.append(it->second); + } else { + unresolved.emplace_back(key); + } + cursor = close + Internal::closeTag.size(); + } + + if (!unresolved.empty()) { + return Err(Internal::FormatUnresolved(unresolved)); + } + return Ok(std::move(result)); + } + + Result TemplateEngine::RenderFile(const std::string& inSrcFile) const + { + return FileUtils::ReadTextFile(inSrcFile) + .AndThen([this](const std::string& inText) { return Render(inText); }); + } + + Result TemplateEngine::RenderFileTo(const std::string& inSrcFile, const std::string& inDstFile) const + { + return RenderFile(inSrcFile) + .AndThen([&inDstFile](const std::string& inRendered) { return FileUtils::WriteTextFile(inDstFile, inRendered); }); + } +} diff --git a/Engine/Source/Common/Test/TemplateTest.cpp b/Engine/Source/Common/Test/TemplateTest.cpp new file mode 100644 index 00000000..71fecf56 --- /dev/null +++ b/Engine/Source/Common/Test/TemplateTest.cpp @@ -0,0 +1,101 @@ +// +// Created by johnk on 2026/6/20. +// + +#include +#include +#include +#include + +using namespace Common; + +TEST(TemplateTest, BasicSubstitutionTest) +{ + TemplateEngine engine; + engine.Set("name", "Explosion").Set("version", "2"); + + const auto result = engine.Render("project({{name}} v{{version}})"); + ASSERT_TRUE(result.IsOk()); + ASSERT_EQ(result.Value(), "project(Explosion v2)"); +} + +TEST(TemplateTest, WhitespaceInsideTagTest) +{ + TemplateEngine engine; + engine.Set("name", "Explosion"); + + const auto result = engine.Render("{{ name }} and {{ name }}"); + ASSERT_TRUE(result.IsOk()); + ASSERT_EQ(result.Value(), "Explosion and Explosion"); +} + +TEST(TemplateTest, BatchSetTest) +{ + TemplateEngine engine; + engine.Set({ { "a", "1" }, { "b", "2" } }); + + const auto result = engine.Render("{{a}}-{{b}}"); + ASSERT_TRUE(result.IsOk()); + ASSERT_EQ(result.Value(), "1-2"); +} + +TEST(TemplateTest, NoTagsTest) +{ + TemplateEngine engine; + + const auto result = engine.Render("plain text with no tags"); + ASSERT_TRUE(result.IsOk()); + ASSERT_EQ(result.Value(), "plain text with no tags"); +} + +TEST(TemplateTest, UndefinedVariableErrorsTest) +{ + TemplateEngine engine; + engine.Set("known", "ok"); + + const auto result = engine.Render("{{ known }}-{{ unknown }}"); + ASSERT_TRUE(result.IsErr()); + ASSERT_NE(result.Error().find("unknown"), std::string::npos); +} + +TEST(TemplateTest, UnterminatedTagErrorsTest) +{ + TemplateEngine engine; + + const auto result = engine.Render("unterminated {{ name"); + ASSERT_TRUE(result.IsErr()); +} + +TEST(TemplateTest, HasAndClearTest) +{ + TemplateEngine engine; + engine.Set("a", "1"); + ASSERT_TRUE(engine.Has("a")); + ASSERT_FALSE(engine.Has("b")); + engine.Clear(); + ASSERT_FALSE(engine.Has("a")); +} + +TEST(TemplateTest, RenderFileTest) +{ + static Common::Path srcFile = "../Test/Generated/Common/TemplateSource.txt"; + static Common::Path dstFile = "../Test/Generated/Common/TemplateOutput.txt"; + + ASSERT_TRUE(FileUtils::WriteTextFile(srcFile.Absolute().String(), "project({{name}})").IsOk()); + + TemplateEngine engine; + engine.Set("name", "Explosion"); + ASSERT_TRUE(engine.RenderFileTo(srcFile.Absolute().String(), dstFile.Absolute().String()).IsOk()); + + const auto content = FileUtils::ReadTextFile(dstFile.Absolute().String()); + ASSERT_TRUE(content.IsOk()); + ASSERT_EQ(content.Value(), "project(Explosion)"); +} + +TEST(TemplateTest, RenderMissingFileErrorsTest) +{ + TemplateEngine engine; + + const auto result = engine.RenderFile("../Test/Generated/Common/DoesNotExist.tpl"); + ASSERT_TRUE(result.IsErr()); +} From e3add8d3c816c53ab04dd4bc78205862079f7735 Mon Sep 17 00:00:00 2001 From: kindem Date: Sun, 21 Jun 2026 01:21:23 +0800 Subject: [PATCH 2/8] refactor: clean up CMake build scripts, helpers and dependency exposure - move Editor web `npm install` out of the configure step into the build-time Editor.Web target so configuration has no network side effects - extract exp_get_runtime_output_dir helper, shared by exp_add_executable and the Editor target instead of duplicating the multi-config output-dir logic - drop `CACHE ... FORCE` on internal derived variables; derive project version from project(Explosion VERSION 0.0.1) - modernise `if (${VAR})` into `if (VAR)`; use built-in WIN32 / APPLE instead of comparing CMAKE_SYSTEM_NAME against string literals - centralize the platform RHI backend target list (platform_rhi_targets) in the root script and reuse it in Sample and Editor - link executables with PRIVATE; clear OUT_DEP_TARGET on the runtime-dep walker early return; remove the redundant macOS Frameworks copy - tighten module dependency exposure: drop unused Taskflow (Common) and assimp (Runtime); move debugbreak / dxc / spirv-cross to PRIVATE; declare Taskflow PRIVATE on Runtime where ECS.cpp actually uses it --- CMake/Common.cmake | 4 +- CMake/Target.cmake | 60 ++++++++++++++---------- CMakeLists.txt | 20 +++++--- Editor/CMakeLists.txt | 62 ++++++------------------- Engine/Source/CMakeLists.txt | 6 +-- Engine/Source/Common/CMakeLists.txt | 3 +- Engine/Source/RHI-Vulkan/CMakeLists.txt | 6 +-- Engine/Source/Render/CMakeLists.txt | 3 +- Engine/Source/Runtime/CMakeLists.txt | 3 +- Sample/CMakeLists.txt | 12 ++--- 10 files changed, 78 insertions(+), 101 deletions(-) diff --git a/CMake/Common.cmake b/CMake/Common.cmake index e871dd0e..9be78563 100644 --- a/CMake/Common.cmake +++ b/CMake/Common.cmake @@ -10,7 +10,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ${EXPORT_COMPILE_COMMANDS}) # position-independent or the shared link fails with "relocation ... can not be used when making a shared object". set(CMAKE_POSITION_INDEPENDENT_CODE ON) -if (${CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT}) +if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/Installed CACHE PATH "" FORCE) endif() @@ -24,7 +24,7 @@ add_compile_definitions( COMPILER_GCC=$,1,0> ) -if (${MSVC}) +if (MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") add_compile_options(/bigobj) add_compile_definitions( diff --git a/CMake/Target.cmake b/CMake/Target.cmake index 8c93bd3c..6d6d9a22 100644 --- a/CMake/Target.cmake +++ b/CMake/Target.cmake @@ -4,22 +4,22 @@ include(CMakePackageConfigHelpers) option(BUILD_TEST "Build unit tests" ON) option(BUILD_BENCHMARK "Build benchmarks" ON) -set(GENERATED_DIR ${CMAKE_BINARY_DIR}/Generated CACHE PATH "" FORCE) -set(GENERATED_API_HEADER_DIR ${GENERATED_DIR}/Api CACHE PATH "" FORCE) -set(GENERATED_MIRROR_INFO_SRC_DIR ${GENERATED_DIR}/MirrorInfoSrc CACHE PATH "" FORCE) -set(BASE_TARGETS_FOLDER "${SUB_PROJECT_NAME}" CACHE STRING "" FORCE) -set(AUX_TARGETS_FOLDER "${BASE_TARGETS_FOLDER}/Aux" CACHE STRING "" FORCE) +set(GENERATED_DIR ${CMAKE_BINARY_DIR}/Generated) +set(GENERATED_API_HEADER_DIR ${GENERATED_DIR}/Api) +set(GENERATED_MIRROR_INFO_SRC_DIR ${GENERATED_DIR}/MirrorInfoSrc) +set(BASE_TARGETS_FOLDER "${SUB_PROJECT_NAME}") +set(AUX_TARGETS_FOLDER "${BASE_TARGETS_FOLDER}/Aux") get_cmake_property(with_multi_config_generator GENERATOR_IS_MULTI_CONFIG) -if (${BUILD_TEST}) +if (BUILD_TEST) enable_testing() add_compile_definitions(BUILD_TEST=1) else() add_compile_definitions(BUILD_TEST=0) endif() -if (${BUILD_BENCHMARK}) +if (BUILD_BENCHMARK) add_compile_definitions(BUILD_BENCHMARK=1) else() add_compile_definitions(BUILD_BENCHMARK=0) @@ -37,6 +37,7 @@ function(exp_gather_target_runtime_dependencies_recurse) if (NOT TARGET ${arg_NAME}) set(${arg_OUT_RUNTIME_DEP} "" PARENT_SCOPE) + set(${arg_OUT_DEP_TARGET} "" PARENT_SCOPE) return() endif () @@ -53,7 +54,7 @@ function(exp_gather_target_runtime_dependencies_recurse) endif() get_target_property(type ${l} TYPE) - if (${type} STREQUAL SHARED_LIBRARY) + if (type STREQUAL "SHARED_LIBRARY") list(APPEND result_runtime_dep $) endif () @@ -114,7 +115,7 @@ function(exp_process_runtime_dependencies) set_target_properties(${custom_target_name} PROPERTIES FOLDER ${AUX_TARGETS_FOLDER}) endif () - if (NOT ${arg_NOT_INSTALL} AND NOT "${runtime_deps}" STREQUAL "") + if (NOT arg_NOT_INSTALL AND NOT "${runtime_deps}" STREQUAL "") install( FILES ${runtime_deps} DESTINATION ${SUB_PROJECT_NAME}/Binaries ) @@ -152,7 +153,7 @@ function(exp_add_resources_copy_command) cmake_path(SET dst_path NORMALIZE "${SUB_PROJECT_NAME}/Binaries/${dst}") cmake_path(GET dst_path PARENT_PATH dst_dir) - if (NOT ${arg_NOT_INSTALL}) + if (NOT arg_NOT_INSTALL) install(FILES ${src} DESTINATION ${dst_dir}) endif () endforeach() @@ -249,7 +250,7 @@ function(exp_add_mirror_info_source_generation_target) list(APPEND fwk_dir_args ${absolute_f}) endforeach () - if (${arg_DYNAMIC}) + if (arg_DYNAMIC) list(APPEND dynamic_arg "-d") endif () @@ -285,13 +286,26 @@ function(exp_add_mirror_info_source_generation_target) endif() endfunction() +function(exp_get_runtime_output_dir) + set(options "") + set(singleValueArgs OUTPUT) + set(multiValueArgs "") + cmake_parse_arguments(arg "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (with_multi_config_generator) + set(${arg_OUTPUT} ${CMAKE_BINARY_DIR}/Dist/$/${SUB_PROJECT_NAME}/Binaries PARENT_SCOPE) + else () + set(${arg_OUTPUT} ${CMAKE_BINARY_DIR}/Dist/${SUB_PROJECT_NAME}/Binaries PARENT_SCOPE) + endif () +endfunction() + function(exp_add_executable) set(options NOT_INSTALL) set(singleValueArgs NAME FOLDER) set(multiValueArgs SRC INC LINK LIB DEP_TARGET RES REFLECT) cmake_parse_arguments(arg "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN}) - if (${arg_NOT_INSTALL}) + if (arg_NOT_INSTALL) set(not_install_flag NOT_INSTALL) else () set(not_install_flag "") @@ -319,11 +333,7 @@ function(exp_add_executable) set_target_properties(${arg_NAME} PROPERTIES FOLDER ${BASE_TARGETS_FOLDER}) endif () - if (${with_multi_config_generator}) - set(runtime_output_dir ${CMAKE_BINARY_DIR}/Dist/$/${SUB_PROJECT_NAME}/Binaries) - else () - set(runtime_output_dir ${CMAKE_BINARY_DIR}/Dist/${SUB_PROJECT_NAME}/Binaries) - endif () + exp_get_runtime_output_dir(OUTPUT runtime_output_dir) set_target_properties( ${arg_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${runtime_output_dir} @@ -339,7 +349,7 @@ function(exp_add_executable) ) target_link_libraries( ${arg_NAME} - PUBLIC ${arg_LIB} + PRIVATE ${arg_LIB} ) exp_process_runtime_dependencies( NAME ${arg_NAME} @@ -358,14 +368,14 @@ function(exp_add_executable) add_dependencies(${arg_NAME} ${generated_target}) endif() - if (${MSVC}) + if (MSVC) set_target_properties( ${arg_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${runtime_output_dir} ) endif() - if (NOT ${arg_NOT_INSTALL}) + if (NOT arg_NOT_INSTALL) install( TARGETS ${arg_NAME} EXPORT ${SUB_PROJECT_NAME}Targets @@ -377,7 +387,7 @@ function(exp_add_executable) APPEND FILE ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}Targets.cmake ) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + if (APPLE) install(CODE "execute_process(COMMAND install_name_tool -add_rpath @executable_path ${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/Binaries/$)") endif () endif () @@ -420,7 +430,7 @@ function(exp_add_library) ${arg_NAME} PRIVATE ${arg_SRC} ${generated_src} ) - if (${with_multi_config_generator}) + if (with_multi_config_generator) set(runtime_output_dir ${CMAKE_BINARY_DIR}/Targets/${SUB_PROJECT_NAME}/${arg_NAME}/$/Binaries) set(library_output_dir ${CMAKE_BINARY_DIR}/Targets/${SUB_PROJECT_NAME}/${arg_NAME}/$/Binaries) set(archive_output_directory ${CMAKE_BINARY_DIR}/Targets/${SUB_PROJECT_NAME}/${arg_NAME}/$/Lib) @@ -476,7 +486,7 @@ function(exp_add_library) add_dependencies(${arg_NAME} ${generated_target}) endif() - if (NOT ${arg_NOT_INSTALL}) + if (NOT arg_NOT_INSTALL) foreach (inc ${arg_PUBLIC_INC}) list(APPEND install_inc ${inc}/) endforeach () @@ -508,7 +518,7 @@ function(exp_add_library) endfunction() function(exp_add_test) - if (NOT ${BUILD_TEST}) + if (NOT BUILD_TEST) return() endif() @@ -537,7 +547,7 @@ function(exp_add_test) endfunction() function(exp_add_benchmark) - if (NOT ${BUILD_BENCHMARK}) + if (NOT BUILD_BENCHMARK) return() endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index f9a742e6..0b4cc515 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,16 +3,16 @@ cmake_minimum_required(VERSION 3.25) set(CONAN_INSTALL_BUILD_CONFIGURATIONS "Release" CACHE STRING "" FORCE) set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES ${CMAKE_SOURCE_DIR}/conan_provider.cmake CACHE PATH "" FORCE) -project(Explosion) +project(Explosion VERSION 0.0.1) option(BUILD_EDITOR "Build Explosion editor" ON) option(BUILD_SAMPLE "Build samples" ON) -set(SUB_PROJECT_NAME "Explosion" CACHE STRING "" FORCE) -set(SUB_PROJECT_VERSION_MAJOR 0 CACHE STRING "" FORCE) -set(SUB_PROJECT_VERSION_MINOR 0 CACHE STRING "" FORCE) -set(SUB_PROJECT_VERSION_PATCH 1 CACHE STRING "" FORCE) -set(SUB_PROJECT_CMAKE_LIBS "ThirdParty/Registry.cmake" CACHE STRING "" FORCE) +set(SUB_PROJECT_NAME "Explosion") +set(SUB_PROJECT_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(SUB_PROJECT_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(SUB_PROJECT_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(SUB_PROJECT_CMAKE_LIBS "ThirdParty/Registry.cmake") set(USE_CONAN ON CACHE BOOL "" FORCE) # Conan builds its dependencies as Release, so map the other configs onto Release imported locations. The trailing @@ -29,10 +29,16 @@ include(CMake/Common.cmake) include(CMake/Target.cmake) include(ThirdParty/Registry.cmake) +if (WIN32) + set(platform_rhi_targets RHI-Vulkan RHI-DirectX12) +else () + set(platform_rhi_targets RHI-Vulkan) +endif () + add_subdirectory(Engine) add_subdirectory(Tool) add_subdirectory(Sample) -if (${BUILD_EDITOR}) +if (BUILD_EDITOR) add_subdirectory(Editor) endif() diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt index c25b9a9e..ef8d3b2c 100644 --- a/Editor/CMakeLists.txt +++ b/Editor/CMakeLists.txt @@ -7,7 +7,7 @@ set(QT_ROOT ${QT6_INSTALL_PREFIX}) qt_standard_project_setup() -if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") +if (APPLE) set(platform_executable_hint MACOSX_BUNDLE) set(bundle_install_dest BUNDLE DESTINATION ${SUB_PROJECT_NAME}/Binaries) set(platform_fwk_dir ${QT_ROOT}/lib) @@ -30,32 +30,20 @@ file(GLOB_RECURSE SOURCES Src/*.cpp) qt_add_executable(Editor ${platform_executable_hint} ${SOURCES} ${EDITOR_MIRROR_GENERATED_SRC}) set_target_properties(Editor PROPERTIES FOLDER ${BASE_TARGETS_FOLDER}) -get_cmake_property(GENERATOR_IS_MULTI_CONFIG GENERATOR_IS_MULTI_CONFIG) -if (${GENERATOR_IS_MULTI_CONFIG}) - set_target_properties( - Editor PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/Dist/$/${SUB_PROJECT_NAME}/Binaries - ) -else () - set_target_properties( - Editor PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/Dist/${SUB_PROJECT_NAME}/Binaries - ) -endif () +exp_get_runtime_output_dir(OUTPUT runtime_output_dir) +set_target_properties( + Editor PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${runtime_output_dir} +) target_include_directories(Editor PRIVATE Include) target_link_libraries(Editor PRIVATE ${editor_libs}) -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - set(RHI_DEP_TARGETS RHI-DirectX12 RHI-Vulkan) -else() - set(RHI_DEP_TARGETS RHI-Vulkan) -endif() -add_dependencies(Editor ${EDITOR_MIRROR_GENERATED_TARGET} ${RHI_DEP_TARGETS}) +add_dependencies(Editor ${EDITOR_MIRROR_GENERATED_TARGET} ${platform_rhi_targets}) exp_process_runtime_dependencies( NAME Editor - DEP_TARGET ${RHI_DEP_TARGETS} + DEP_TARGET ${platform_rhi_targets} ) install( TARGETS Editor @@ -68,7 +56,7 @@ export( APPEND FILE ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}Targets.cmake ) -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") +if (WIN32) set(qt_win_deploy_executable ${QT_ROOT}/bin/windeployqt.exe) add_custom_command( TARGET Editor POST_BUILD @@ -77,7 +65,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") install( CODE "execute_process(COMMAND ${qt_win_deploy_executable} ${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/Binaries/$)" ) -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") +elseif (APPLE) set(qt_mac_deploy_executable ${QT_ROOT}/bin/macdeployqt) add_custom_command( TARGET Editor POST_BUILD @@ -88,18 +76,6 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") ) endif () -# TODO check is this need ? -if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - foreach (rhi_dep_target ${RHI_DEP_TARGETS}) - list(APPEND rhi_dep_target_copy_commands COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $/../Frameworks/$) - endforeach () - - add_custom_command( - TARGET Editor POST_BUILD - ${rhi_dep_target_copy_commands} - ) -endif () - # ---- begin shaders --------------------------------------------------------------------------------- get_engine_shader_resources(OUTPUT editor_resources) @@ -124,27 +100,15 @@ exp_add_resources_copy_command( # ---- end shaders ----------------------------------------------------------------------------------- # ---- begin web project ----------------------------------------------------------------------------- -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") +if (WIN32) find_program(npm_executable NAMES npm.cmd npm REQUIRED NO_CACHE) else () find_program(npm_executable NAMES npm REQUIRED NO_CACHE) endif () -message("Perform web project npm install......") -execute_process( - COMMAND ${CMAKE_COMMAND} -E env ${npm_executable} install --no-fund --registry=https://registry.npmmirror.com - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Web - RESULT_VARIABLE npm_install_result - OUTPUT_VARIABLE npm_install_output - ERROR_VARIABLE npm_install_error -) -if (${npm_install_result} STREQUAL "0") - message(${npm_install_output}) -else () - message(FATAL_ERROR ${npm_install_error}) -endif () add_custom_target( Editor.Web + COMMAND ${CMAKE_COMMAND} -E env ${npm_executable} install --no-fund --registry=https://registry.npmmirror.com COMMAND ${CMAKE_COMMAND} -E env ${npm_executable} run build WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Web VERBATIM @@ -156,7 +120,7 @@ add_custom_command( TARGET Editor.Web POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different ${CMAKE_CURRENT_SOURCE_DIR}/Web/dist $/Web ) -if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") +if (NOT APPLE) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different ${CMAKE_CURRENT_SOURCE_DIR}/Web/dist ${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/Binaries/Web)") endif () # ---- end web project ------------------------------------------------------------------------------- diff --git a/Engine/Source/CMakeLists.txt b/Engine/Source/CMakeLists.txt index 6880b60e..a6fc2880 100644 --- a/Engine/Source/CMakeLists.txt +++ b/Engine/Source/CMakeLists.txt @@ -1,8 +1,8 @@ -if (${BUILD_TEST}) +if (BUILD_TEST) add_subdirectory(Test) endif() -if (${BUILD_BENCHMARK}) +if (BUILD_BENCHMARK) add_subdirectory(Benchmark) endif() @@ -13,7 +13,7 @@ add_subdirectory(Mirror) add_subdirectory(RHI) add_subdirectory(RHI-Dummy) add_subdirectory(RHI-Vulkan) -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") +if (WIN32) add_subdirectory(RHI-DirectX12) endif() diff --git a/Engine/Source/Common/CMakeLists.txt b/Engine/Source/Common/CMakeLists.txt index 5b7f1648..248fe865 100644 --- a/Engine/Source/Common/CMakeLists.txt +++ b/Engine/Source/Common/CMakeLists.txt @@ -17,7 +17,8 @@ exp_add_library( TYPE STATIC SRC ${sources} PUBLIC_INC Include - PUBLIC_LIB rapidjson debugbreak::debugbreak cityhash::cityhash Taskflow::Taskflow + PUBLIC_LIB rapidjson cityhash::cityhash + PRIVATE_LIB debugbreak::debugbreak PUBLIC_COMPILE_OPT ${math_public_compile_opt} ) diff --git a/Engine/Source/RHI-Vulkan/CMakeLists.txt b/Engine/Source/RHI-Vulkan/CMakeLists.txt index 9930cb16..b872d14c 100644 --- a/Engine/Source/RHI-Vulkan/CMakeLists.txt +++ b/Engine/Source/RHI-Vulkan/CMakeLists.txt @@ -1,8 +1,8 @@ file(GLOB sources Src/*.cpp) -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") +if (WIN32) set(platform_sources Src/Platform/Win32Surface.cpp) -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") +elseif (APPLE) set(platform_sources Src/Platform/MacosSurface.mm) set(platform_ext_libs molten-vk::molten-vk "-framework Cocoa" "-framework IOKit" "-framework CoreFoundation") endif() @@ -16,7 +16,7 @@ exp_add_library( ) # .mm files can not perform unity build with .cpp files -if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") +if (APPLE) set_target_properties( RHI-Vulkan PROPERTIES UNITY_BUILD OFF) diff --git a/Engine/Source/Render/CMakeLists.txt b/Engine/Source/Render/CMakeLists.txt index f2f25cc3..021ca780 100644 --- a/Engine/Source/Render/CMakeLists.txt +++ b/Engine/Source/Render/CMakeLists.txt @@ -4,7 +4,8 @@ exp_add_library( TYPE STATIC SRC ${sources} PUBLIC_INC Include - PUBLIC_LIB Core RHI dxc::dxc spirv-cross::spirv-cross + PUBLIC_LIB Core RHI + PRIVATE_LIB dxc::dxc spirv-cross::spirv-cross ) file(GLOB shader_sources SharedSrc/*.cpp) diff --git a/Engine/Source/Runtime/CMakeLists.txt b/Engine/Source/Runtime/CMakeLists.txt index 66316f76..5c5a45a3 100644 --- a/Engine/Source/Runtime/CMakeLists.txt +++ b/Engine/Source/Runtime/CMakeLists.txt @@ -5,7 +5,8 @@ exp_add_library( SRC ${sources} PUBLIC_INC Include REFLECT Include - PUBLIC_LIB Core Mirror Render assimp::assimp + PUBLIC_LIB Core Mirror Render + PRIVATE_LIB Taskflow::Taskflow ) file(GLOB test_sources Test/*.cpp) diff --git a/Sample/CMakeLists.txt b/Sample/CMakeLists.txt index 2b512a8b..61c35c0b 100644 --- a/Sample/CMakeLists.txt +++ b/Sample/CMakeLists.txt @@ -4,16 +4,10 @@ function(add_sample) set(multiValueArgs SRC INC SHADER IMAGE MODEL) cmake_parse_arguments(arg "${options}" "${singleValueArgs}" "${multiValueArgs}" ${ARGN}) - if (NOT ${BUILD_SAMPLE}) + if (NOT BUILD_SAMPLE) return() endif () - if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - set(platform_dep_target RHI-DirectX12 RHI-Vulkan) - else() - set(platform_dep_target RHI-Vulkan) - endif() - foreach(s ${arg_SHADER}) set(path "${CMAKE_CURRENT_SOURCE_DIR}/${s}->../Test/Sample/${s}") list(APPEND paths ${path}) @@ -37,13 +31,13 @@ function(add_sample) SRC ${arg_SRC} INC ${arg_INC} LIB Sample-Base - DEP_TARGET ${platform_dep_target} + DEP_TARGET ${platform_rhi_targets} RES ${paths} NOT_INSTALL ) endfunction() -if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") +if (APPLE) set(platform_ext_libs "-framework Cocoa" "-framework IOKit" From 10498a09238f079cf641e3bb3592ebdb54043fcf Mon Sep 17 00:00:00 2001 From: kindem Date: Sun, 21 Jun 2026 10:58:10 +0800 Subject: [PATCH 3/8] ci: build and install TestProject against the installed engine --- .github/workflows/build.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8395b6c..2b0aaf27 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,4 +103,13 @@ jobs: - name: Install run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target install -j ${{env.MAKE_THREAD_NUM}} - # TODO build test project + # Build the standalone TestProject against the freshly installed engine to verify the packaged engine (its + # ExplosionConfig.cmake and full_deploy'd third-party dependencies) is consumable by a downstream project. + - name: Configure Test Project + run: cmake -B ${{github.workspace}}/TestProject/build -S ${{github.workspace}}/TestProject -G=Ninja -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENGINE_ROOT=${{github.workspace}}/Installed/Explosion + + - name: Build Test Project + run: cmake --build ${{github.workspace}}/TestProject/build --config ${{env.BUILD_TYPE}} -j ${{env.MAKE_THREAD_NUM}} + + - name: Install Test Project + run: cmake --build ${{github.workspace}}/TestProject/build --config ${{env.BUILD_TYPE}} --target install -j ${{env.MAKE_THREAD_NUM}} From 0d558fa1b3ee8572840bc943baff2eaa7f3ff227 Mon Sep 17 00:00:00 2001 From: kindem Date: Sun, 21 Jun 2026 11:27:14 +0800 Subject: [PATCH 4/8] refactor: stop emitting package export rules in downstream installs The exp_* helpers are installed with the engine and re-included by downstream projects through find_package(Explosion). Their target-export and package-config rules therefore ran a second time in downstream context, installing each game/plugin's own Config/ConfigVersion/Targets files plus a copy of the engine CMake helpers - none of which a terminal consumer needs. Detect that context via CMAKE_FIND_PACKAGE_NAME (set only while the consumed package config runs) and gate all export/package-config emission on it, so the engine still exports its package while downstream install trees keep only their runtime binaries. Library/header/runtime installs are untouched, so downstream shared libraries (plugins, multi-DLL games) still install; this also avoids the install(EXPORT) error such a library would otherwise hit on the imported Explosion:: targets in its link interface. --- CMake/Target.cmake | 149 ++++++++++++++++++++++++++------------------- 1 file changed, 88 insertions(+), 61 deletions(-) diff --git a/CMake/Target.cmake b/CMake/Target.cmake index 6d6d9a22..a123096d 100644 --- a/CMake/Target.cmake +++ b/CMake/Target.cmake @@ -29,6 +29,17 @@ if ("${SUB_PROJECT_NAME}" STREQUAL "") message(FATAL_ERROR "SUB_PROJECT_NAME not defined, please set it in your project cmake") endif () +# The engine build includes this file directly and exports its targets so downstream projects can find_package() it. +# Those downstream projects pull these helpers back in transitively while their package config runs (CMake sets +# CMAKE_FIND_PACKAGE_NAME during that include), and they are terminal consumers - games or plugins nobody find_package()s +# - so they only ship their runtime binaries, never their own package/export files. Detecting that here keeps the export +# machinery out of downstream install trees without any per-project switch. +if (CMAKE_FIND_PACKAGE_NAME) + set(export_sub_project_package OFF) +else () + set(export_sub_project_package ON) +endif () + function(exp_gather_target_runtime_dependencies_recurse) set(options "") set(singleValueArgs NAME OUT_RUNTIME_DEP OUT_DEP_TARGET) @@ -376,16 +387,23 @@ function(exp_add_executable) endif() if (NOT arg_NOT_INSTALL) + if (export_sub_project_package) + set(export_args EXPORT ${SUB_PROJECT_NAME}Targets) + else () + set(export_args "") + endif () install( TARGETS ${arg_NAME} - EXPORT ${SUB_PROJECT_NAME}Targets + ${export_args} RUNTIME DESTINATION ${SUB_PROJECT_NAME}/Binaries ) - export( - TARGETS ${arg_NAME} - NAMESPACE ${SUB_PROJECT_NAME}:: - APPEND FILE ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}Targets.cmake - ) + if (export_sub_project_package) + export( + TARGETS ${arg_NAME} + NAMESPACE ${SUB_PROJECT_NAME}:: + APPEND FILE ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}Targets.cmake + ) + endif () if (APPLE) install(CODE "execute_process(COMMAND install_name_tool -add_rpath @executable_path ${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/Binaries/$)") @@ -495,18 +513,25 @@ function(exp_add_library) DESTINATION ${SUB_PROJECT_NAME}/Target/${arg_NAME}/Include ) + if (export_sub_project_package) + set(export_args EXPORT ${SUB_PROJECT_NAME}Targets) + else () + set(export_args "") + endif () install( TARGETS ${arg_NAME} - EXPORT ${SUB_PROJECT_NAME}Targets + ${export_args} ARCHIVE DESTINATION ${SUB_PROJECT_NAME}/Target/${arg_NAME}/Lib LIBRARY DESTINATION ${SUB_PROJECT_NAME}/Target/${arg_NAME}/Lib RUNTIME DESTINATION ${SUB_PROJECT_NAME}/Target/${arg_NAME}/Binaries ) - export( - TARGETS ${arg_NAME} - NAMESPACE ${SUB_PROJECT_NAME}:: - APPEND FILE ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}Targets.cmake - ) + if (export_sub_project_package) + export( + TARGETS ${arg_NAME} + NAMESPACE ${SUB_PROJECT_NAME}:: + APPEND FILE ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}Targets.cmake + ) + endif () if ("${arg_TYPE}" STREQUAL "SHARED") install( @@ -570,61 +595,63 @@ function(exp_add_benchmark) ) endfunction() -install( - EXPORT ${SUB_PROJECT_NAME}Targets - FILE ${SUB_PROJECT_NAME}Targets.cmake - NAMESPACE ${SUB_PROJECT_NAME}:: - DESTINATION ${SUB_PROJECT_NAME} -) - -configure_package_config_file( - ${CMAKE_CURRENT_LIST_DIR}/Config.cmake.in - ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}Config.cmake - INSTALL_DESTINATION ${SUB_PROJECT_NAME}/CMake -) - -write_basic_package_version_file( - ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}ConfigVersion.cmake - VERSION ${SUB_PROJECT_VERSION_MAJOR}.${SUB_PROJECT_VERSION_MINOR}.${SUB_PROJECT_VERSION_PATCH} - COMPATIBILITY SameMajorVersion -) - -install( - FILES +if (export_sub_project_package) + install( + EXPORT ${SUB_PROJECT_NAME}Targets + FILE ${SUB_PROJECT_NAME}Targets.cmake + NAMESPACE ${SUB_PROJECT_NAME}:: + DESTINATION ${SUB_PROJECT_NAME} + ) + + configure_package_config_file( + ${CMAKE_CURRENT_LIST_DIR}/Config.cmake.in ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}Config.cmake + INSTALL_DESTINATION ${SUB_PROJECT_NAME}/CMake + ) + + write_basic_package_version_file( ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}ConfigVersion.cmake - DESTINATION ${SUB_PROJECT_NAME} -) - -file(GLOB_RECURSE preset_cmake_libs ${CMAKE_CURRENT_LIST_DIR}/*) -foreach (cmake_lib ${preset_cmake_libs}) - file( - COPY ${cmake_lib} - DESTINATION ${CMAKE_BINARY_DIR}/CMake + VERSION ${SUB_PROJECT_VERSION_MAJOR}.${SUB_PROJECT_VERSION_MINOR}.${SUB_PROJECT_VERSION_PATCH} + COMPATIBILITY SameMajorVersion ) -endforeach () -install( - FILES ${preset_cmake_libs} - DESTINATION ${SUB_PROJECT_NAME}/CMake -) - -if (DEFINED SUB_PROJECT_CMAKE_LIBS) - foreach (cmake_lib ${SUB_PROJECT_CMAKE_LIBS}) - if (IS_ABSOLUTE ${cmake_lib}) - message(FATAL_ERROR "project cmake libs defined in SUB_PROJECT_CMAKE_LIBS should be relative path from project root") - endif () - set(src_file ${CMAKE_SOURCE_DIR}/${cmake_lib}) - get_filename_component(binary_tree_dst_dir ${CMAKE_BINARY_DIR}/CMake/${cmake_lib} DIRECTORY) - get_filename_component(install_tree_dst_dir ${SUB_PROJECT_NAME}/CMake/${cmake_lib} DIRECTORY) + install( + FILES + ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}Config.cmake + ${CMAKE_BINARY_DIR}/${SUB_PROJECT_NAME}ConfigVersion.cmake + DESTINATION ${SUB_PROJECT_NAME} + ) + file(GLOB_RECURSE preset_cmake_libs ${CMAKE_CURRENT_LIST_DIR}/*) + foreach (cmake_lib ${preset_cmake_libs}) file( - COPY ${src_file} - DESTINATION ${binary_tree_dst_dir} - ) - install( - FILES ${src_file} - DESTINATION ${install_tree_dst_dir} + COPY ${cmake_lib} + DESTINATION ${CMAKE_BINARY_DIR}/CMake ) endforeach () + install( + FILES ${preset_cmake_libs} + DESTINATION ${SUB_PROJECT_NAME}/CMake + ) + + if (DEFINED SUB_PROJECT_CMAKE_LIBS) + foreach (cmake_lib ${SUB_PROJECT_CMAKE_LIBS}) + if (IS_ABSOLUTE ${cmake_lib}) + message(FATAL_ERROR "project cmake libs defined in SUB_PROJECT_CMAKE_LIBS should be relative path from project root") + endif () + + set(src_file ${CMAKE_SOURCE_DIR}/${cmake_lib}) + get_filename_component(binary_tree_dst_dir ${CMAKE_BINARY_DIR}/CMake/${cmake_lib} DIRECTORY) + get_filename_component(install_tree_dst_dir ${SUB_PROJECT_NAME}/CMake/${cmake_lib} DIRECTORY) + + file( + COPY ${src_file} + DESTINATION ${binary_tree_dst_dir} + ) + install( + FILES ${src_file} + DESTINATION ${install_tree_dst_dir} + ) + endforeach () + endif () endif () From 234210211464d1096f90a2a73ccab7f3d90fd370 Mon Sep 17 00:00:00 2001 From: kindem Date: Sun, 21 Jun 2026 11:44:12 +0800 Subject: [PATCH 5/8] refactor: move TestProject Main.cpp into Game/Src --- TestProject/CMakeLists.txt | 2 +- TestProject/{ => Game/Src}/Main.cpp | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename TestProject/{ => Game/Src}/Main.cpp (100%) diff --git a/TestProject/CMakeLists.txt b/TestProject/CMakeLists.txt index bc5a8fe1..e0e0250a 100644 --- a/TestProject/CMakeLists.txt +++ b/TestProject/CMakeLists.txt @@ -13,6 +13,6 @@ find_package(Explosion REQUIRED) exp_add_executable( NAME Main - SRC Main.cpp + SRC Game/Src/Main.cpp LIB Explosion::Core ) diff --git a/TestProject/Main.cpp b/TestProject/Game/Src/Main.cpp similarity index 100% rename from TestProject/Main.cpp rename to TestProject/Game/Src/Main.cpp From 9855ceb579d9bf6dc75aab1c32a463d2abd573b6 Mon Sep 17 00:00:00 2001 From: kindem Date: Sun, 21 Jun 2026 11:50:12 +0800 Subject: [PATCH 6/8] feat: flesh out the Default project template from TestProject --- .../ProjectTemplates/Default/CMakeLists.txt.tpl | 16 +++++++++++++++- .../ProjectTemplates/Default/Game/Src/Main.cpp | 7 +++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 Editor/Resource/ProjectTemplates/Default/Game/Src/Main.cpp diff --git a/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl b/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl index 094d4407..f66976d1 100644 --- a/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl +++ b/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl @@ -1,4 +1,18 @@ cmake_minimum_required(VERSION {{cmakeMinVersion}}) + project({{projectName}}) -{{findEngineCMakeScripts}} +set(ENGINE_ROOT "" CACHE STRING "Root directory of engine (where ExplosionConfig.cmake inside), support source build engine or packed engine.") +set(SUB_PROJECT_NAME "{{projectName}}" CACHE STRING "Name of project." FORCE) +if ("${ENGINE_ROOT}" STREQUAL "") + message(FATAL_ERROR "ENGINE_ROOT not set, please add -DENGINE_ROOT=/path/to/engine/root to cmake arguments.") +endif () + +set(CMAKE_PREFIX_PATH ${ENGINE_ROOT}) +find_package(Explosion REQUIRED) + +exp_add_executable( + NAME Main + SRC Game/Src/Main.cpp + LIB Explosion::Core +) diff --git a/Editor/Resource/ProjectTemplates/Default/Game/Src/Main.cpp b/Editor/Resource/ProjectTemplates/Default/Game/Src/Main.cpp new file mode 100644 index 00000000..592b3f5a --- /dev/null +++ b/Editor/Resource/ProjectTemplates/Default/Game/Src/Main.cpp @@ -0,0 +1,7 @@ +#include + +int main() +{ + LogInfo(Project, "Hello, explosion"); + return 0; +} From 606db1020484b0bb5aad30fd87e6e0dfa56599db Mon Sep 17 00:00:00 2001 From: kindem Date: Sun, 21 Jun 2026 12:18:55 +0800 Subject: [PATCH 7/8] refactor: render the test project from the editor Default template Switch Common::TemplateEngine to '@var@' placeholders so CMake's configure_file(@ONLY) renders the same templates, and have Editor/CMakeLists.txt instantiate the Default template into TestProject/ at configure time. Drops the duplicated standalone TestProject sources; local and CI builds now generate it. --- .github/workflows/build.yml | 5 +++-- .gitignore | 7 ++----- Editor/CMakeLists.txt | 19 +++++++++++++++++++ .../Default/CMakeLists.txt.tpl | 6 +++--- Engine/Source/Common/Src/Template.cpp | 6 ++++-- Engine/Source/Common/Test/TemplateTest.cpp | 12 ++++++------ TestProject/CMakeLists.txt | 18 ------------------ TestProject/Game/Src/Main.cpp | 7 ------- 8 files changed, 37 insertions(+), 43 deletions(-) delete mode 100644 TestProject/CMakeLists.txt delete mode 100644 TestProject/Game/Src/Main.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b0aaf27..6dff019b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,8 +103,9 @@ jobs: - name: Install run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target install -j ${{env.MAKE_THREAD_NUM}} - # Build the standalone TestProject against the freshly installed engine to verify the packaged engine (its - # ExplosionConfig.cmake and full_deploy'd third-party dependencies) is consumable by a downstream project. + # Build the test project (generated from the editor's Default template during 'Configure CMake') against the + # freshly installed engine, verifying the packaged engine and its full_deploy'd dependencies are consumable by + # a downstream project. - name: Configure Test Project run: cmake -B ${{github.workspace}}/TestProject/build -S ${{github.workspace}}/TestProject -G=Ninja -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENGINE_ROOT=${{github.workspace}}/Installed/Explosion diff --git a/.gitignore b/.gitignore index b73ee422..8a228113 100644 --- a/.gitignore +++ b/.gitignore @@ -23,11 +23,8 @@ aqtinstall.log # Claude CLAUDE.md -# Test Project -TestProject/.idea -TestProject/.vscode -TestProject/cmake-build* -TestProject/build* +# Test Project (generated from the editor's Default project template by CI / tooling) +/TestProject # macOS .DS_Store diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt index ef8d3b2c..aa7f874c 100644 --- a/Editor/CMakeLists.txt +++ b/Editor/CMakeLists.txt @@ -124,3 +124,22 @@ if (NOT APPLE) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different ${CMAKE_CURRENT_SOURCE_DIR}/Web/dist ${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/Binaries/Web)") endif () # ---- end web project ------------------------------------------------------------------------------- + +# ---- begin test project ---------------------------------------------------------------------------- +# Render the Default project template into TestProject/ at configure time, so local and CI builds can build it +# against the installed engine. The variable names must match the @placeholders@ in the template. +set(projectName ExplosionTest) +set(cmakeMinVersion 3.25) + +set(project_template_dir ${CMAKE_CURRENT_SOURCE_DIR}/Resource/ProjectTemplates/Default) +set(test_project_dir ${CMAKE_SOURCE_DIR}/TestProject) +file(GLOB_RECURSE project_template_files RELATIVE ${project_template_dir} ${project_template_dir}/*) +foreach (template_file ${project_template_files}) + if (template_file MATCHES "\\.tpl$") + string(REGEX REPLACE "\\.tpl$" "" rendered_file ${template_file}) + configure_file(${project_template_dir}/${template_file} ${test_project_dir}/${rendered_file} @ONLY) + else () + configure_file(${project_template_dir}/${template_file} ${test_project_dir}/${template_file} COPYONLY) + endif () +endforeach () +# ---- end test project ------------------------------------------------------------------------------ diff --git a/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl b/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl index f66976d1..01591b09 100644 --- a/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl +++ b/Editor/Resource/ProjectTemplates/Default/CMakeLists.txt.tpl @@ -1,9 +1,9 @@ -cmake_minimum_required(VERSION {{cmakeMinVersion}}) +cmake_minimum_required(VERSION @cmakeMinVersion@) -project({{projectName}}) +project(@projectName@) set(ENGINE_ROOT "" CACHE STRING "Root directory of engine (where ExplosionConfig.cmake inside), support source build engine or packed engine.") -set(SUB_PROJECT_NAME "{{projectName}}" CACHE STRING "Name of project." FORCE) +set(SUB_PROJECT_NAME "@projectName@" CACHE STRING "Name of project." FORCE) if ("${ENGINE_ROOT}" STREQUAL "") message(FATAL_ERROR "ENGINE_ROOT not set, please add -DENGINE_ROOT=/path/to/engine/root to cmake arguments.") endif () diff --git a/Engine/Source/Common/Src/Template.cpp b/Engine/Source/Common/Src/Template.cpp index 3914d666..1086cad1 100644 --- a/Engine/Source/Common/Src/Template.cpp +++ b/Engine/Source/Common/Src/Template.cpp @@ -10,8 +10,10 @@ #include namespace Common::Internal { - constexpr std::string_view openTag = "{{"; - constexpr std::string_view closeTag = "}}"; + // '@var@' placeholders mirror CMake's configure_file(@ONLY), so the same templates render identically here and + // CMake-side. + constexpr std::string_view openTag = "@"; + constexpr std::string_view closeTag = "@"; static std::string Trim(const std::string& inStr) { diff --git a/Engine/Source/Common/Test/TemplateTest.cpp b/Engine/Source/Common/Test/TemplateTest.cpp index 71fecf56..08cebed8 100644 --- a/Engine/Source/Common/Test/TemplateTest.cpp +++ b/Engine/Source/Common/Test/TemplateTest.cpp @@ -14,7 +14,7 @@ TEST(TemplateTest, BasicSubstitutionTest) TemplateEngine engine; engine.Set("name", "Explosion").Set("version", "2"); - const auto result = engine.Render("project({{name}} v{{version}})"); + const auto result = engine.Render("project(@name@ v@version@)"); ASSERT_TRUE(result.IsOk()); ASSERT_EQ(result.Value(), "project(Explosion v2)"); } @@ -24,7 +24,7 @@ TEST(TemplateTest, WhitespaceInsideTagTest) TemplateEngine engine; engine.Set("name", "Explosion"); - const auto result = engine.Render("{{ name }} and {{ name }}"); + const auto result = engine.Render("@ name @ and @ name @"); ASSERT_TRUE(result.IsOk()); ASSERT_EQ(result.Value(), "Explosion and Explosion"); } @@ -34,7 +34,7 @@ TEST(TemplateTest, BatchSetTest) TemplateEngine engine; engine.Set({ { "a", "1" }, { "b", "2" } }); - const auto result = engine.Render("{{a}}-{{b}}"); + const auto result = engine.Render("@a@-@b@"); ASSERT_TRUE(result.IsOk()); ASSERT_EQ(result.Value(), "1-2"); } @@ -53,7 +53,7 @@ TEST(TemplateTest, UndefinedVariableErrorsTest) TemplateEngine engine; engine.Set("known", "ok"); - const auto result = engine.Render("{{ known }}-{{ unknown }}"); + const auto result = engine.Render("@ known @-@ unknown @"); ASSERT_TRUE(result.IsErr()); ASSERT_NE(result.Error().find("unknown"), std::string::npos); } @@ -62,7 +62,7 @@ TEST(TemplateTest, UnterminatedTagErrorsTest) { TemplateEngine engine; - const auto result = engine.Render("unterminated {{ name"); + const auto result = engine.Render("unterminated @ name"); ASSERT_TRUE(result.IsErr()); } @@ -81,7 +81,7 @@ TEST(TemplateTest, RenderFileTest) static Common::Path srcFile = "../Test/Generated/Common/TemplateSource.txt"; static Common::Path dstFile = "../Test/Generated/Common/TemplateOutput.txt"; - ASSERT_TRUE(FileUtils::WriteTextFile(srcFile.Absolute().String(), "project({{name}})").IsOk()); + ASSERT_TRUE(FileUtils::WriteTextFile(srcFile.Absolute().String(), "project(@name@)").IsOk()); TemplateEngine engine; engine.Set("name", "Explosion"); diff --git a/TestProject/CMakeLists.txt b/TestProject/CMakeLists.txt deleted file mode 100644 index e0e0250a..00000000 --- a/TestProject/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -cmake_minimum_required(VERSION 3.25) - -project(ExplosionTest) - -set(ENGINE_ROOT "" CACHE STRING "Root directory of engine (where ExplosionConfig.cmake inside), support source build engine or packed engine.") -set(SUB_PROJECT_NAME "ExplosionTest" CACHE STRING "Name of project." FORCE) -if ("${ENGINE_ROOT}" STREQUAL "") - message(FATAL_ERROR "ENGINE_ROOT not set, please add -DENGINE_ROOT=/path/to/engine/root to cmake arguments.") -endif () - -set(CMAKE_PREFIX_PATH ${ENGINE_ROOT}) -find_package(Explosion REQUIRED) - -exp_add_executable( - NAME Main - SRC Game/Src/Main.cpp - LIB Explosion::Core -) diff --git a/TestProject/Game/Src/Main.cpp b/TestProject/Game/Src/Main.cpp deleted file mode 100644 index 592b3f5a..00000000 --- a/TestProject/Game/Src/Main.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -int main() -{ - LogInfo(Project, "Hello, explosion"); - return 0; -} From 989c17bc08e677cec3f5df296a73efa854270d3e Mon Sep 17 00:00:00 2001 From: kindem Date: Sun, 21 Jun 2026 13:20:19 +0800 Subject: [PATCH 8/8] fix: deploy installed dependencies with the engine conan profile --- ThirdParty/Registry.cmake | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ThirdParty/Registry.cmake b/ThirdParty/Registry.cmake index f8550eba..53e007e3 100644 --- a/ThirdParty/Registry.cmake +++ b/ThirdParty/Registry.cmake @@ -1,5 +1,10 @@ if (${USE_CONAN}) - install(CODE "execute_process(COMMAND conan install ${CMAKE_SOURCE_DIR} -c tools.cmake.cmakedeps:new=will_break_next --deployer=full_deploy --output-folder=${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/ThirdParty)") + # Deploy the dependencies into the install tree so downstream projects (which take the else() branch below) can + # find_package() them. This must reuse the very host profile cmake-conan generated for the engine build: that + # profile pins compiler.cppstd to CMAKE_CXX_STANDARD, so resolving with the bare default profile instead would + # compute different package ids and fail the deploy - either as "invalid packages" where the default cppstd is + # lower than a dependency requires, or as a "missing binary" mismatch against the binaries already in the cache. + install(CODE "execute_process(COMMAND conan install ${CMAKE_SOURCE_DIR} --profile:host=default --profile:host=${CMAKE_BINARY_DIR}/conan_host_profile --profile:build=default -s build_type=Release -c tools.cmake.cmakedeps:new=will_break_next --deployer=full_deploy --output-folder=${CMAKE_INSTALL_PREFIX}/${SUB_PROJECT_NAME}/ThirdParty COMMAND_ERROR_IS_FATAL ANY)") else () set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH};${CMAKE_CURRENT_LIST_DIR}/../../ThirdParty") endif ()