结构优化

This commit is contained in:
Rowland 2026-07-14 09:24:04 +08:00
parent 278313f739
commit b10e03eda0
25 changed files with 1682 additions and 31 deletions

View File

@ -1,4 +1,6 @@
#include "MetaCorePlatform/MetaCoreWindow.h"
#include "MetaCoreDelivery/MetaCoreBuildPipeline.h"
#include "MetaCoreDelivery/MetaCoreCrashReporter.h"
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include "MetaCoreFoundation/MetaCoreProject.h"
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
@ -12,6 +14,7 @@
#include "MetaCoreScene/MetaCoreScene.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <unordered_map>
@ -19,6 +22,44 @@
namespace {
class MetaCoreTeeBuffer final : public std::streambuf {
public:
MetaCoreTeeBuffer(std::streambuf* first, std::streambuf* second) : First_(first), Second_(second) {}
protected:
int overflow(int character) override {
if (character == traits_type::eof()) return traits_type::not_eof(character);
const bool firstOk = First_ == nullptr || First_->sputc(static_cast<char>(character)) != traits_type::eof();
const bool secondOk = Second_ == nullptr || Second_->sputc(static_cast<char>(character)) != traits_type::eof();
return firstOk && secondOk ? character : traits_type::eof();
}
int sync() override {
return (First_ == nullptr || First_->pubsync() == 0) && (Second_ == nullptr || Second_->pubsync() == 0) ? 0 : -1;
}
private:
std::streambuf* First_ = nullptr;
std::streambuf* Second_ = nullptr;
};
[[nodiscard]] std::filesystem::path MetaCoreGetExecutableRoot(int argc, char* argv[]) {
#if defined(__linux__)
std::error_code error;
const auto executable = std::filesystem::canonical("/proc/self/exe", error);
if (!error) return executable.parent_path();
#endif
if (argc > 0) {
std::error_code error;
return std::filesystem::absolute(argv[0], error).parent_path();
}
return std::filesystem::current_path();
}
void MetaCoreRotatePlayerLog(const std::filesystem::path& path) {
std::error_code error;
if (std::filesystem::is_regular_file(path, error) && std::filesystem::file_size(path, error) >= 5U * 1024U * 1024U) {
std::filesystem::rename(path, path.string() + ".1", error);
}
}
MetaCore::MetaCoreSceneView MetaCoreBuildPlayerSceneView() {
MetaCore::MetaCoreSceneView sceneView;
sceneView.CameraPosition = {0.0F, 2.5F, 6.5F};
@ -196,6 +237,9 @@ void MetaCoreResolveRuntimeSourcePathsForProject(
int main(int argc, char* argv[]) {
std::filesystem::path customProjectRoot{};
std::filesystem::path customScenePath{};
std::filesystem::path runtimeProjectPath{};
bool validateOnly = false;
std::uint64_t smokeTestFrames = 0U;
for (int i = 1; i < argc; ++i) {
if (std::strcmp(argv[i], "--project") == 0 && i + 1 < argc) {
customProjectRoot = argv[i + 1];
@ -203,38 +247,95 @@ int main(int argc, char* argv[]) {
} else if (std::strcmp(argv[i], "--scene") == 0 && i + 1 < argc) {
customScenePath = argv[i + 1];
++i;
} else if (std::strcmp(argv[i], "--runtime-project") == 0 && i + 1 < argc) {
runtimeProjectPath = argv[++i];
} else if (std::strcmp(argv[i], "--validate-only") == 0) {
validateOnly = true;
} else if (std::strcmp(argv[i], "--smoke-test-frames") == 0 && i + 1 < argc) {
smokeTestFrames = static_cast<std::uint64_t>(std::strtoull(argv[++i], nullptr, 10));
}
}
if (argc > 0) {
std::filesystem::current_path(std::filesystem::path(argv[0]).parent_path());
const std::filesystem::path executableRoot = MetaCoreGetExecutableRoot(argc, argv);
if (runtimeProjectPath.empty() && customProjectRoot.empty() &&
std::filesystem::is_regular_file(executableRoot / "Project" / "MetaCore.runtimeproject")) {
runtimeProjectPath = executableRoot / "Project" / "MetaCore.runtimeproject";
}
std::optional<MetaCore::MetaCoreRuntimeDeliveryDocument> deliveryDocument;
std::filesystem::path packageRoot;
if (!runtimeProjectPath.empty()) {
runtimeProjectPath = runtimeProjectPath.is_absolute() ? runtimeProjectPath : std::filesystem::absolute(runtimeProjectPath);
packageRoot = runtimeProjectPath.parent_path().parent_path();
MetaCore::MetaCoreBuildIssue issue;
deliveryDocument = MetaCore::MetaCoreReadRuntimeDeliveryDocument(runtimeProjectPath, &issue);
if (!deliveryDocument) {
std::cerr << MetaCore::MetaCoreBuildErrorCodeName(issue.Code) << ": " << issue.Message << " path=" << issue.Path << '\n';
return 2;
}
const auto validationIssues = MetaCore::MetaCoreValidateReleasePackage(packageRoot);
if (!validationIssues.empty()) {
for (const auto& validationIssue : validationIssues) {
std::cerr << MetaCore::MetaCoreBuildErrorCodeName(validationIssue.Code) << ": " << validationIssue.Message
<< " path=" << validationIssue.Path << '\n';
}
return 2;
}
if (validateOnly) {
std::cout << "MetaCorePlayer: delivery package validation passed build_id=" << deliveryDocument->BuildId << '\n';
return 0;
}
} else if (validateOnly) {
std::cerr << "MCB1001: --validate-only requires a packaged runtime project\n";
return 2;
}
std::filesystem::current_path(deliveryDocument ? packageRoot : executableRoot);
const auto diagnosticsRoot = deliveryDocument ? packageRoot / "Diagnostics" : executableRoot / "Diagnostics";
std::error_code logError;
std::filesystem::create_directories(diagnosticsRoot / "Logs", logError);
const auto logPath = diagnosticsRoot / "Logs" / "MetaCorePlayer.log";
MetaCoreRotatePlayerLog(logPath);
std::ofstream logFile(logPath, std::ios::app);
MetaCoreTeeBuffer coutBuffer(std::cout.rdbuf(), logFile.rdbuf());
MetaCoreTeeBuffer cerrBuffer(std::cerr.rdbuf(), logFile.rdbuf());
std::ostream output(&coutBuffer);
std::ostream errors(&cerrBuffer);
output << "MetaCorePlayer: start version=1.0.0 platform=Linux-x86_64 build_id="
<< (deliveryDocument ? deliveryDocument->BuildId : "development") << '\n';
if (!MetaCore::MetaCoreInitializeCrashReporter(
executableRoot,
diagnosticsRoot,
deliveryDocument ? deliveryDocument->BuildId : "development")) {
output << "MetaCorePlayer: Crashpad handler unavailable; Minidump capture disabled\n";
}
MetaCore::MetaCoreWindow window;
if (!window.Initialize(1280, 720, "MetaCore Player")) {
std::cerr << "MetaCorePlayer: window initialize failed\n";
errors << "MetaCorePlayer: window initialize failed\n";
return 1;
}
MetaCore::MetaCoreRenderDevice renderDevice;
if (!renderDevice.Initialize(window)) {
std::cerr << "MetaCorePlayer: render device initialize failed\n";
errors << "MetaCorePlayer: render device initialize failed\n";
return 1;
}
MetaCore::MetaCoreEditorViewportRenderer viewportRenderer;
if (!viewportRenderer.Initialize(renderDevice, window, false)) {
std::cerr << "MetaCorePlayer: viewport renderer initialize failed\n";
errors << "MetaCorePlayer: viewport renderer initialize failed\n";
return 1;
}
std::filesystem::path projectRoot;
if (!customProjectRoot.empty()) {
if (deliveryDocument) {
projectRoot = packageRoot / deliveryDocument->ContentRoot;
} else if (!customProjectRoot.empty()) {
projectRoot = std::filesystem::absolute(customProjectRoot);
} else {
const auto discovered = MetaCore::MetaCoreDiscoverProjectRoot();
if (!discovered.has_value()) {
std::cerr << "MetaCorePlayer: failed to discover project root\n";
errors << "MetaCorePlayer: failed to discover project root\n";
return 1;
}
projectRoot = *discovered;
@ -244,7 +345,7 @@ int main(int argc, char* argv[]) {
MetaCore::MetaCoreTypeRegistry typeRegistry;
MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(typeRegistry);
const auto loadedRuntimeProjectDocument = MetaCore::MetaCoreReadRuntimeProjectDocument(
projectRoot / "Runtime" / "ProjectRuntime.mcruntimecfg",
deliveryDocument ? packageRoot / deliveryDocument->RuntimeConfig : projectRoot / "Runtime" / "ProjectRuntime.mcruntimecfg",
typeRegistry
);
const auto runtimeProjectDocument = loadedRuntimeProjectDocument.value_or(MetaCoreBuildDefaultRuntimeProjectDocument());
@ -254,10 +355,13 @@ int main(int argc, char* argv[]) {
const auto absoluteScene = customScenePath.is_absolute() ? customScenePath : (projectRoot / customScenePath);
startupSceneDocument = MetaCore::MetaCoreReadScenePackage(absoluteScene);
}
if (!startupSceneDocument.has_value() && !runtimeProjectDocument.StartupScenePath.empty()) {
if (!startupSceneDocument.has_value() && deliveryDocument) {
startupSceneDocument = MetaCore::MetaCoreReadScenePackage(packageRoot / deliveryDocument->StartupScene);
}
if (!startupSceneDocument.has_value() && !deliveryDocument && !runtimeProjectDocument.StartupScenePath.empty()) {
startupSceneDocument = MetaCore::MetaCoreReadScenePackage(projectRoot / runtimeProjectDocument.StartupScenePath);
}
if (!startupSceneDocument.has_value()) {
if (!startupSceneDocument.has_value() && !deliveryDocument) {
startupSceneDocument = MetaCore::MetaCoreLoadStartupSceneDocument(projectPath);
}
MetaCore::MetaCoreScene scene;
@ -265,11 +369,14 @@ int main(int argc, char* argv[]) {
MetaCore::MetaCoreSceneSnapshot snapshot;
snapshot.GameObjects = startupSceneDocument->GameObjects;
scene.RestoreSnapshot(snapshot);
std::cout << "MetaCorePlayer: loaded startup scene from project "
output << "MetaCorePlayer: loaded startup scene from project "
<< projectPath.string() << '\n';
} else {
} else if (!deliveryDocument) {
scene = MetaCore::MetaCoreCreateDefaultScene();
std::cout << "MetaCorePlayer: startup scene unavailable, using built-in default scene\n";
output << "MetaCorePlayer: startup scene unavailable, using built-in default scene\n";
} else {
errors << "MCB6004: packaged startup scene is missing or unreadable path=" << deliveryDocument->StartupScene << '\n';
return 2;
}
MetaCore::MetaCoreRuntimeDataDispatcher runtimeDataDispatcher(scene);
@ -289,25 +396,31 @@ int main(int argc, char* argv[]) {
MetaCore::MetaCoreRuntimeUiSystem runtimeUi;
if (loadedUiManifest.has_value()) {
if (!runtimeUi.Initialize(window, projectRoot, projectRoot / "Ui", *loadedUiManifest)) {
std::cerr << "MetaCorePlayer: failed to initialize runtime UI\n";
errors << "MetaCorePlayer: failed to initialize runtime UI\n";
} else {
runtimeUi.AttachToViewportRenderer(viewportRenderer);
}
for (const std::string& error : runtimeUi.GetErrors()) {
std::cerr << "MetaCorePlayer UI: " << error << '\n';
errors << "MetaCorePlayer UI: " << error << '\n';
}
}
if (loadedSourcesDocument.has_value() && loadedBindingsDocument.has_value()) {
std::cout << "MetaCorePlayer: loaded runtime config from binary .mcruntime documents\n";
output << "MetaCorePlayer: loaded runtime config from binary .mcruntime documents\n";
} else {
const bool sourcesExists = std::filesystem::exists(sourcesPath);
const bool bindingsExists = std::filesystem::exists(bindingsPath);
if ((sourcesExists && !loadedSourcesDocument.has_value()) ||
(bindingsExists && !loadedBindingsDocument.has_value())) {
std::cerr << "MetaCorePlayer: runtime config exists but is unreadable or corrupted, using built-in fallback config\n";
errors << "MetaCorePlayer: runtime config exists but is unreadable or corrupted"
<< (deliveryDocument ? "\n" : ", using built-in fallback config\n");
if (deliveryDocument) return 2;
} else {
std::cout << "MetaCorePlayer: runtime config missing, using built-in fallback config\n";
if (deliveryDocument) {
errors << "MCB6004: packaged runtime configuration is missing\n";
return 2;
}
output << "MetaCorePlayer: runtime config missing, using built-in fallback config\n";
}
}
@ -318,17 +431,17 @@ int main(int argc, char* argv[]) {
if (!sourcesDocument.Sources.empty()) {
runtimeAdapter = MetaCore::MetaCoreCreateRuntimeDataSourceAdapter(sourcesDocument.Sources.front().AdapterType);
if (runtimeAdapter == nullptr) {
std::cerr << "MetaCorePlayer: unsupported adapter type "
errors << "MetaCorePlayer: unsupported adapter type "
<< sourcesDocument.Sources.front().AdapterType << '\n';
return 1;
}
if (!runtimeAdapter->Configure(sourcesDocument.Sources.front())) {
std::cerr << "MetaCorePlayer: failed to configure runtime adapter error="
errors << "MetaCorePlayer: failed to configure runtime adapter error="
<< runtimeAdapter->GetStatus().LastError << '\n';
return 1;
}
if (!runtimeAdapter->Connect()) {
std::cerr << "MetaCorePlayer: failed to connect runtime adapter error="
errors << "MetaCorePlayer: failed to connect runtime adapter error="
<< runtimeAdapter->GetStatus().LastError << '\n';
return 1;
}
@ -341,6 +454,7 @@ int main(int argc, char* argv[]) {
std::unordered_map<std::string, std::string> lastReportedBindingIssueKeys;
std::uint64_t diagnosticsWriteFrame = 0;
std::uint64_t renderedFrames = 0U;
while (!window.ShouldClose()) {
window.BeginFrame();
const auto [windowWidth, windowHeight] = window.GetWindowSize();
@ -367,7 +481,7 @@ int main(int argc, char* argv[]) {
}
++diagnosticsWriteFrame;
if (runtimeAdapter != nullptr && runtimeAdapter->GetStatus().State != lastReportedSourceState) {
std::cout << "MetaCorePlayer: data source state changed to "
output << "MetaCorePlayer: data source state changed to "
<< static_cast<int>(runtimeAdapter->GetStatus().State)
<< " error=" << runtimeAdapter->GetStatus().LastError << '\n';
lastReportedSourceState = runtimeAdapter->GetStatus().State;
@ -384,7 +498,7 @@ int main(int argc, char* argv[]) {
const auto previousIssue = lastReportedBindingIssueKeys.find(bindingStatus.BindingId);
if (previousIssue == lastReportedBindingIssueKeys.end() ||
previousIssue->second != issueKey) {
std::cout << "MetaCorePlayer: binding issue id=" << bindingStatus.BindingId
output << "MetaCorePlayer: binding issue id=" << bindingStatus.BindingId
<< " stale=" << (bindingStatus.Stale ? "true" : "false")
<< " error=" << bindingStatus.LastError << '\n';
lastReportedBindingIssueKeys[bindingStatus.BindingId] = issueKey;
@ -392,7 +506,7 @@ int main(int argc, char* argv[]) {
} else {
const auto previousIssue = lastReportedBindingIssueKeys.find(bindingStatus.BindingId);
if (previousIssue != lastReportedBindingIssueKeys.end()) {
std::cout << "MetaCorePlayer: binding recovered id=" << bindingStatus.BindingId << '\n';
output << "MetaCorePlayer: binding recovered id=" << bindingStatus.BindingId << '\n';
lastReportedBindingIssueKeys.erase(previousIssue);
}
}
@ -404,6 +518,8 @@ int main(int argc, char* argv[]) {
renderDevice.RenderFrame();
renderDevice.PresentFrame();
window.EndFrame();
++renderedFrames;
if (smokeTestFrames > 0U && renderedFrames >= smokeTestFrames) break;
}
runtimeUi.Shutdown();

View File

@ -68,6 +68,7 @@ endif()
find_package(glm CONFIG REQUIRED)
find_package(crashpad CONFIG QUIET)
set(METACORE_IMGUI_USES_PKG_CONFIG FALSE)
if(NOT METACORE_BUILD_CORE_ONLY)
include(MetaCoreFilament)
@ -477,6 +478,36 @@ target_link_libraries(MetaCoreScene
target_compile_options(MetaCoreScene PRIVATE ${METACORE_COMMON_WARNINGS})
add_library(MetaCoreDelivery STATIC
Source/MetaCoreDelivery/Public/MetaCoreDelivery/MetaCoreBuildPipeline.h
Source/MetaCoreDelivery/Public/MetaCoreDelivery/MetaCoreCrashReporter.h
Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp
Source/MetaCoreDelivery/Private/MetaCoreCrashReporter.cpp
)
target_include_directories(MetaCoreDelivery
PUBLIC Source/MetaCoreDelivery/Public
PRIVATE third_party
)
target_link_libraries(MetaCoreDelivery
PUBLIC MetaCoreFoundation MetaCoreScene
)
target_compile_options(MetaCoreDelivery PRIVATE ${METACORE_COMMON_WARNINGS})
if(crashpad_FOUND)
target_link_libraries(MetaCoreDelivery PUBLIC crashpad::crashpad)
target_compile_definitions(MetaCoreDelivery PUBLIC METACORE_HAS_CRASHPAD=1)
else()
target_compile_definitions(MetaCoreDelivery PUBLIC METACORE_HAS_CRASHPAD=0)
message(WARNING "Crashpad was not found; release packaging works but local Minidump capture is disabled.")
endif()
add_executable(MetaCoreBuildTool tools/MetaCoreBuildTool/main.cpp)
target_link_libraries(MetaCoreBuildTool PRIVATE MetaCoreDelivery)
target_compile_options(MetaCoreBuildTool PRIVATE ${METACORE_COMMON_WARNINGS})
add_executable(MetaCoreDiagnosticsTool tools/MetaCoreDiagnosticsTool/main.cpp)
target_link_libraries(MetaCoreDiagnosticsTool PRIVATE MetaCoreDelivery)
target_compile_options(MetaCoreDiagnosticsTool PRIVATE ${METACORE_COMMON_WARNINGS})
if(NOT METACORE_BUILD_CORE_ONLY)
set(METACORE_RENDER_HEADERS
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreEditorViewportRenderer.h
@ -573,6 +604,7 @@ target_link_libraries(MetaCoreRuntimeConfigTool
MetaCoreFoundation
MetaCoreRuntimeData
MetaCoreScene
MetaCoreDelivery
)
endif()
@ -654,6 +686,7 @@ target_link_libraries(MetaCoreEditor
MetaCoreRender
MetaCoreRuntimeData
MetaCoreScene
MetaCoreDelivery
glm::glm
imgui::imgui
)
@ -740,7 +773,14 @@ target_link_libraries(MetaCorePlayer
MetaCoreRuntimeData
MetaCoreScene
MetaCoreRuntimeUi
MetaCoreDelivery
)
if(UNIX AND NOT APPLE)
set_target_properties(MetaCorePlayer PROPERTIES
BUILD_RPATH "\$ORIGIN/lib"
INSTALL_RPATH "\$ORIGIN/lib"
)
endif()
metacore_stage_ui_blit_material(MetaCorePlayer)
metacore_stage_rml_ui_material(MetaCorePlayer)
@ -749,6 +789,11 @@ metacore_stage_rml_ui_material(MetaCorePlayer)
if(METACORE_BUILD_TESTS)
enable_testing()
add_executable(MetaCoreDeliveryTests tests/MetaCoreDeliveryTests.cpp)
target_link_libraries(MetaCoreDeliveryTests PRIVATE MetaCoreDelivery)
target_compile_options(MetaCoreDeliveryTests PRIVATE ${METACORE_COMMON_WARNINGS})
add_test(NAME MetaCoreDeliveryTests COMMAND MetaCoreDeliveryTests)
add_executable(MetaCoreSmokeTests
tests/MetaCoreSmokeTests.cpp
Source/MetaCoreRender/Private/MetaCoreGlibcCompat.cpp

View File

@ -32,6 +32,32 @@
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "linux-base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"cacheVariables": {
"CMAKE_CXX_STANDARD": "20",
"VCPKG_TARGET_TRIPLET": "x64-linux-clang-libcxx",
"VCPKG_OVERLAY_TRIPLETS": "${sourceDir}/vcpkg-triplets",
"METACORE_BUILD_TESTS": "ON"
},
"condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux" }
},
{
"name": "linux-development",
"inherits": "linux-base",
"displayName": "Linux x64 Development",
"cacheVariables": { "CMAKE_BUILD_TYPE": "RelWithDebInfo" }
},
{
"name": "linux-release",
"inherits": "linux-base",
"displayName": "Linux x64 Release",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
}
],
"buildPresets": [
@ -44,6 +70,14 @@
"name": "build-release",
"configurePreset": "vs2022-release",
"configuration": "Release"
},
{
"name": "build-linux-development",
"configurePreset": "linux-development"
},
{
"name": "build-linux-release",
"configurePreset": "linux-release"
}
],
"testPresets": [
@ -54,6 +88,11 @@
"output": {
"outputOnFailure": true
}
},
{
"name": "test-linux-development",
"configurePreset": "linux-development",
"output": { "outputOnFailure": true }
}
]
}

View File

@ -0,0 +1,690 @@
#include "MetaCoreDelivery/MetaCoreBuildPipeline.h"
#include "MetaCoreFoundation/MetaCoreHash.h"
#include "MetaCoreFoundation/MetaCorePackage.h"
#include "MetaCoreFoundation/MetaCoreProject.h"
#include "MetaCoreScene/MetaCoreScenePackage.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include <nlohmann/json.hpp>
#include <algorithm>
#include <array>
#include <chrono>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <set>
#include <sstream>
#include <system_error>
#if defined(__linux__)
#include <sys/utsname.h>
#endif
namespace MetaCore {
namespace {
using json = nlohmann::json;
[[nodiscard]] std::string ConfigurationName(MetaCoreBuildConfiguration configuration) {
switch (configuration) {
case MetaCoreBuildConfiguration::Debug: return "Debug";
case MetaCoreBuildConfiguration::Development: return "Development";
case MetaCoreBuildConfiguration::Release: return "Release";
}
return "Development";
}
[[nodiscard]] std::string CMakeConfigurationName(MetaCoreBuildConfiguration configuration) {
switch (configuration) {
case MetaCoreBuildConfiguration::Debug: return "Debug";
case MetaCoreBuildConfiguration::Development: return "RelWithDebInfo";
case MetaCoreBuildConfiguration::Release: return "Release";
}
return "RelWithDebInfo";
}
[[nodiscard]] bool IsSafeRelativePath(const std::filesystem::path& path) {
if (path.empty() || path.is_absolute()) return false;
const auto normalized = path.lexically_normal();
return normalized != ".." && (normalized.empty() || *normalized.begin() != "..");
}
[[nodiscard]] bool IsSafeIdentifier(std::string_view value) {
if (value.empty()) return false;
return std::all_of(value.begin(), value.end(), [](unsigned char character) {
return std::isalnum(character) != 0 || character == '-' || character == '_' || character == '.';
});
}
[[nodiscard]] std::string ShellQuote(const std::filesystem::path& path) {
std::string result = "'";
for (const char character : path.string()) {
result += character == '\'' ? "'\\''" : std::string(1, character);
}
result += "'";
return result;
}
[[nodiscard]] bool RunCommand(const std::string& command) {
return std::system(command.c_str()) == 0;
}
[[nodiscard]] std::optional<json> ReadJson(const std::filesystem::path& path) {
try {
std::ifstream input(path);
if (!input.is_open()) return std::nullopt;
json value;
input >> value;
return value;
} catch (...) {
return std::nullopt;
}
}
[[nodiscard]] bool WriteJson(const std::filesystem::path& path, const json& value) {
std::error_code error;
std::filesystem::create_directories(path.parent_path(), error);
if (error) return false;
std::ofstream output(path, std::ios::trunc);
if (!output.is_open()) return false;
output << value.dump(2) << '\n';
return output.good();
}
void SetIssue(MetaCoreBuildIssue* issue, MetaCoreBuildErrorCode code, std::string message, const std::filesystem::path& path = {}) {
if (issue == nullptr) return;
issue->Code = code;
issue->Message = std::move(message);
issue->Path = path;
}
[[nodiscard]] std::string MakeBuildId() {
const auto now = std::chrono::system_clock::now();
const std::time_t time = std::chrono::system_clock::to_time_t(now);
std::tm utc{};
#if defined(_WIN32)
gmtime_s(&utc, &time);
#else
gmtime_r(&time, &utc);
#endif
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count() % 1000;
std::ostringstream output;
output << std::put_time(&utc, "%Y%m%dT%H%M%S") << std::setw(3) << std::setfill('0') << milliseconds << "Z";
return output.str();
}
[[nodiscard]] std::filesystem::path FindSourceRoot(std::filesystem::path start) {
std::error_code error;
start = std::filesystem::absolute(start, error);
while (!start.empty()) {
if (std::filesystem::exists(start / "CMakeLists.txt", error) && std::filesystem::exists(start / "Source", error)) return start;
const auto parent = start.parent_path();
if (parent == start) break;
start = parent;
}
return {};
}
[[nodiscard]] bool CopyTree(const std::filesystem::path& source, const std::filesystem::path& target) {
std::error_code error;
if (!std::filesystem::is_directory(source, error)) return false;
std::filesystem::create_directories(target, error);
if (error) return false;
for (std::filesystem::recursive_directory_iterator iterator(source, error), end; iterator != end; iterator.increment(error)) {
if (error) return false;
const auto relative = iterator->path().lexically_relative(source);
if (!IsSafeRelativePath(relative)) return false;
const auto destination = target / relative;
if (iterator->is_directory(error)) {
std::filesystem::create_directories(destination, error);
} else if (iterator->is_regular_file(error)) {
std::filesystem::create_directories(destination.parent_path(), error);
std::filesystem::copy_file(iterator->path(), destination, std::filesystem::copy_options::overwrite_existing, error);
}
if (error) return false;
}
return true;
}
[[nodiscard]] std::filesystem::path FindPlayer(const std::filesystem::path& buildRoot, MetaCoreBuildConfiguration configuration) {
const std::array candidates = {
buildRoot / "MetaCorePlayer",
buildRoot / CMakeConfigurationName(configuration) / "MetaCorePlayer",
buildRoot / "MetaCorePlayer.exe",
buildRoot / CMakeConfigurationName(configuration) / "MetaCorePlayer.exe"
};
for (const auto& candidate : candidates) if (std::filesystem::is_regular_file(candidate)) return candidate;
return {};
}
[[nodiscard]] std::string DetectGitCommit(const std::filesystem::path& sourceRoot) {
const auto head = ReadJson(sourceRoot / ".metacore-build-metadata.json");
if (head && head->contains("git_commit")) return head->value("git_commit", "unknown");
std::ifstream input(sourceRoot / ".git" / "HEAD");
std::string value;
std::getline(input, value);
if (value.rfind("ref: ", 0) == 0) {
std::ifstream reference(sourceRoot / ".git" / value.substr(5));
std::getline(reference, value);
}
return value.empty() ? "unknown" : value;
}
[[nodiscard]] std::string RoleForPath(const std::filesystem::path& path) {
const std::string value = path.generic_string();
if (value == "MetaCorePlayer" || value == "MetaCorePlayer.exe") return "player";
if (value.rfind("lib/", 0) == 0) return "runtime_library";
if (value.rfind("Engine/Materials/", 0) == 0) return "engine_material";
if (value.rfind("Engine/Fonts/", 0) == 0) return "font";
if (value.rfind("Content/Scenes/", 0) == 0) return "scene";
if (value.rfind("Content/Ui/", 0) == 0) return "ui";
if (value.rfind("Content/Runtime/", 0) == 0) return "runtime_config";
if (value.rfind("Content/Assets/", 0) == 0) return "asset";
if (value == "Project/MetaCore.runtimeproject") return "runtime_project";
return "content";
}
[[nodiscard]] std::vector<MetaCoreReleaseFileEntry> CollectManifestFiles(const std::filesystem::path& root) {
std::vector<MetaCoreReleaseFileEntry> files;
std::error_code error;
for (std::filesystem::recursive_directory_iterator iterator(root, error), end; iterator != end; iterator.increment(error)) {
if (error) break;
if (!iterator->is_regular_file(error)) continue;
const auto relative = iterator->path().lexically_relative(root);
if (relative.generic_string().rfind("Manifest/", 0) == 0 || relative.generic_string().rfind("Diagnostics/", 0) == 0) continue;
const auto hash = MetaCoreSha256File(iterator->path());
if (!hash) continue;
files.push_back({relative, RoleForPath(relative), iterator->file_size(error), *hash, true});
}
std::sort(files.begin(), files.end(), [](const auto& left, const auto& right) {
return left.Path.generic_string() < right.Path.generic_string();
});
return files;
}
[[nodiscard]] bool CopyLinuxRuntimeLibraries(const std::filesystem::path& player, const std::filesystem::path& libraryRoot) {
#if defined(__linux__)
const std::string command = "ldd " + ShellQuote(player);
FILE* pipe = popen(command.c_str(), "r");
if (pipe == nullptr) return false;
std::set<std::filesystem::path> libraries;
bool dependencyMissing = false;
std::array<char, 4096> buffer{};
while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe) != nullptr) {
const std::string line(buffer.data());
if (line.find("=> not found") != std::string::npos) dependencyMissing = true;
const std::size_t arrow = line.find("=> /");
if (arrow == std::string::npos) continue;
const std::size_t begin = arrow + 3U;
const std::size_t end = line.find(' ', begin);
const std::filesystem::path dependency = line.substr(begin, end - begin);
const std::string filename = dependency.filename().string();
if (filename.rfind("libc++", 0) == 0 || filename.rfind("libunwind", 0) == 0) libraries.insert(dependency);
}
const int status = pclose(pipe);
if (status != 0 || dependencyMissing) return false;
std::error_code error;
std::filesystem::create_directories(libraryRoot, error);
for (const auto& library : libraries) {
const auto canonical = std::filesystem::canonical(library, error);
if (error) return false;
std::filesystem::copy_file(canonical, libraryRoot / library.filename(), std::filesystem::copy_options::overwrite_existing, error);
if (error) return false;
}
#else
(void)player;
(void)libraryRoot;
#endif
return true;
}
[[nodiscard]] bool CreateArchive(const std::filesystem::path& archive, const std::filesystem::path& directory) {
const std::string command = "cd " + ShellQuote(directory.parent_path()) + " && cmake -E tar czf " +
ShellQuote(std::filesystem::absolute(archive)) + " -- " + ShellQuote(directory.filename());
return RunCommand(command);
}
} // namespace
const char* MetaCoreBuildErrorCodeName(MetaCoreBuildErrorCode code) {
switch (code) {
case MetaCoreBuildErrorCode::None: return "MCB0000";
case MetaCoreBuildErrorCode::InvalidArguments: return "MCB1001";
case MetaCoreBuildErrorCode::InvalidProfile: return "MCB1002";
case MetaCoreBuildErrorCode::UnsupportedPlatform: return "MCB1003";
case MetaCoreBuildErrorCode::UnsafePath: return "MCB1004";
case MetaCoreBuildErrorCode::ProjectUnreadable: return "MCB1005";
case MetaCoreBuildErrorCode::BuildFailed: return "MCB2001";
case MetaCoreBuildErrorCode::CookFailed: return "MCB3001";
case MetaCoreBuildErrorCode::StageFailed: return "MCB4001";
case MetaCoreBuildErrorCode::PackageFailed: return "MCB5001";
case MetaCoreBuildErrorCode::ManifestUnreadable: return "MCB6001";
case MetaCoreBuildErrorCode::ManifestVersionUnsupported: return "MCB6002";
case MetaCoreBuildErrorCode::RuntimeAbiIncompatible: return "MCB6003";
case MetaCoreBuildErrorCode::RequiredFileMissing: return "MCB6004";
case MetaCoreBuildErrorCode::FileSizeMismatch: return "MCB6005";
case MetaCoreBuildErrorCode::FileHashMismatch: return "MCB6006";
case MetaCoreBuildErrorCode::DependencyMissing: return "MCB6007";
case MetaCoreBuildErrorCode::Cancelled: return "MCB9001";
}
return "MCB9999";
}
const char* MetaCoreBuildStageName(MetaCoreBuildStage stage) {
switch (stage) {
case MetaCoreBuildStage::Build: return "Build";
case MetaCoreBuildStage::Cook: return "Cook";
case MetaCoreBuildStage::Stage: return "Stage";
case MetaCoreBuildStage::Package: return "Package";
case MetaCoreBuildStage::Validate: return "Validate";
}
return "Unknown";
}
std::optional<MetaCoreBuildProfile> MetaCoreReadBuildProfile(const std::filesystem::path& path, MetaCoreBuildIssue* issue) {
const auto value = ReadJson(path);
if (!value) {
SetIssue(issue, MetaCoreBuildErrorCode::InvalidProfile, "Build profile is missing or invalid JSON", path);
return std::nullopt;
}
MetaCoreBuildProfile profile;
profile.FormatVersion = value->value("format_version", 0U);
profile.Name = value->value("name", "");
profile.TargetPlatform = value->value("target_platform", "");
profile.GraphicsBackend = value->value("graphics_backend", "");
profile.StartupScene = value->value("startup_scene", "");
profile.OutputDirectory = value->value("output_directory", "Build");
const std::string configuration = value->value("configuration", "Development");
if (configuration == "Debug") profile.Configuration = MetaCoreBuildConfiguration::Debug;
else if (configuration == "Release") profile.Configuration = MetaCoreBuildConfiguration::Release;
else if (configuration == "Development") profile.Configuration = MetaCoreBuildConfiguration::Development;
else {
SetIssue(issue, MetaCoreBuildErrorCode::InvalidProfile, "Unknown build configuration", path);
return std::nullopt;
}
const std::string policy = value->value("resource_policy", "ReferencedOnly");
if (policy == "AllProjectContent") profile.ResourcePolicy = MetaCoreBuildResourcePolicy::AllProjectContent;
else if (policy != "ReferencedOnly") {
SetIssue(issue, MetaCoreBuildErrorCode::InvalidProfile, "Unknown resource policy", path);
return std::nullopt;
}
if (profile.FormatVersion != 1U || !IsSafeIdentifier(profile.Name) || !IsSafeRelativePath(profile.OutputDirectory) ||
(!profile.StartupScene.empty() && !IsSafeRelativePath(profile.StartupScene))) {
SetIssue(issue, MetaCoreBuildErrorCode::UnsafePath, "Build profile contains an unsupported version or unsafe path", path);
return std::nullopt;
}
return profile;
}
bool MetaCoreWriteBuildProfile(const std::filesystem::path& path, const MetaCoreBuildProfile& profile) {
return WriteJson(path, {
{"format_version", profile.FormatVersion}, {"name", profile.Name}, {"target_platform", profile.TargetPlatform},
{"configuration", ConfigurationName(profile.Configuration)}, {"graphics_backend", profile.GraphicsBackend},
{"startup_scene", profile.StartupScene.generic_string()}, {"output_directory", profile.OutputDirectory.generic_string()},
{"resource_policy", profile.ResourcePolicy == MetaCoreBuildResourcePolicy::ReferencedOnly ? "ReferencedOnly" : "AllProjectContent"}
});
}
std::optional<MetaCoreRuntimeDeliveryDocument> MetaCoreReadRuntimeDeliveryDocument(const std::filesystem::path& path, MetaCoreBuildIssue* issue) {
const auto value = ReadJson(path);
if (!value) {
SetIssue(issue, MetaCoreBuildErrorCode::ManifestUnreadable, "Runtime delivery document is missing or invalid", path);
return std::nullopt;
}
MetaCoreRuntimeDeliveryDocument document;
document.FormatVersion = value->value("format_version", 0U);
document.RuntimeAbiMajor = value->value("runtime_abi_major", 0U);
document.RuntimeAbiMinor = value->value("runtime_abi_minor", 0U);
document.BuildId = value->value("build_id", "");
document.ContentRoot = value->value("content_root", "");
document.StartupScene = value->value("startup_scene", "");
document.RuntimeConfig = value->value("runtime_config", "");
document.UiManifest = value->value("ui_manifest", "");
document.ReleaseManifest = value->value("release_manifest", "");
if (document.FormatVersion != GMetaCoreRuntimeDeliveryVersion) {
SetIssue(issue, MetaCoreBuildErrorCode::ManifestVersionUnsupported, "Runtime delivery format is unsupported", path);
return std::nullopt;
}
if (document.RuntimeAbiMajor != GMetaCoreRuntimeAbiMajor) {
SetIssue(issue, MetaCoreBuildErrorCode::RuntimeAbiIncompatible, "Runtime ABI major is incompatible", path);
return std::nullopt;
}
for (const auto& relative : {document.ContentRoot, document.StartupScene, document.RuntimeConfig, document.UiManifest, document.ReleaseManifest}) {
if (!relative.empty() && !IsSafeRelativePath(relative)) {
SetIssue(issue, MetaCoreBuildErrorCode::UnsafePath, "Runtime delivery document contains an unsafe path", path);
return std::nullopt;
}
}
return document;
}
bool MetaCoreWriteRuntimeDeliveryDocument(const std::filesystem::path& path, const MetaCoreRuntimeDeliveryDocument& document) {
return WriteJson(path, {
{"format_version", document.FormatVersion}, {"runtime_abi_major", document.RuntimeAbiMajor},
{"runtime_abi_minor", document.RuntimeAbiMinor}, {"build_id", document.BuildId},
{"content_root", document.ContentRoot.generic_string()}, {"startup_scene", document.StartupScene.generic_string()},
{"runtime_config", document.RuntimeConfig.generic_string()}, {"ui_manifest", document.UiManifest.generic_string()},
{"release_manifest", document.ReleaseManifest.generic_string()}
});
}
std::optional<MetaCoreReleaseManifestDocument> MetaCoreReadReleaseManifest(const std::filesystem::path& path, MetaCoreBuildIssue* issue) {
const auto value = ReadJson(path);
if (!value) {
SetIssue(issue, MetaCoreBuildErrorCode::ManifestUnreadable, "Release manifest is missing or invalid", path);
return std::nullopt;
}
MetaCoreReleaseManifestDocument document;
document.FormatVersion = value->value("format_version", 0U);
document.RuntimeAbiMajor = value->value("runtime_abi_major", 0U);
document.RuntimeAbiMinor = value->value("runtime_abi_minor", 0U);
document.PackageFormatVersion = value->value("package_format_version", 0U);
document.EngineVersion = value->value("engine_version", "");
document.ProjectName = value->value("project_name", "");
document.ProjectVersion = value->value("project_version", "");
document.BuildId = value->value("build_id", "");
document.GitCommit = value->value("git_commit", "");
document.TargetPlatform = value->value("target_platform", "");
document.Configuration = value->value("configuration", "");
document.GraphicsBackend = value->value("graphics_backend", "");
document.SystemDependencies = value->value("system_dependencies", std::vector<std::string>{});
if (value->contains("files") && (*value)["files"].is_array()) {
for (const auto& file : (*value)["files"]) {
MetaCoreReleaseFileEntry entry;
entry.Path = file.value("path", ""); entry.Role = file.value("role", "");
entry.Size = file.value("size", 0ULL); entry.Sha256 = file.value("sha256", ""); entry.Required = file.value("required", true);
document.Files.push_back(std::move(entry));
}
}
if (document.FormatVersion != GMetaCoreReleaseManifestVersion) {
SetIssue(issue, MetaCoreBuildErrorCode::ManifestVersionUnsupported, "Release manifest format is unsupported", path);
return std::nullopt;
}
if (document.RuntimeAbiMajor != GMetaCoreRuntimeAbiMajor) {
SetIssue(issue, MetaCoreBuildErrorCode::RuntimeAbiIncompatible, "Release runtime ABI major is incompatible", path);
return std::nullopt;
}
return document;
}
bool MetaCoreWriteReleaseManifest(const std::filesystem::path& path, const MetaCoreReleaseManifestDocument& document) {
json files = json::array();
for (const auto& file : document.Files) files.push_back({
{"path", file.Path.generic_string()}, {"role", file.Role}, {"size", file.Size}, {"sha256", file.Sha256}, {"required", file.Required}
});
return WriteJson(path, {
{"format_version", document.FormatVersion}, {"runtime_abi_major", document.RuntimeAbiMajor},
{"runtime_abi_minor", document.RuntimeAbiMinor}, {"package_format_version", document.PackageFormatVersion},
{"engine_version", document.EngineVersion}, {"project_name", document.ProjectName}, {"project_version", document.ProjectVersion},
{"build_id", document.BuildId}, {"git_commit", document.GitCommit}, {"target_platform", document.TargetPlatform},
{"configuration", document.Configuration}, {"graphics_backend", document.GraphicsBackend},
{"system_dependencies", document.SystemDependencies}, {"files", files}
});
}
std::vector<MetaCoreBuildIssue> MetaCoreValidateReleasePackage(const std::filesystem::path& packageRoot) {
std::vector<MetaCoreBuildIssue> issues;
MetaCoreBuildIssue manifestIssue;
const auto manifest = MetaCoreReadReleaseManifest(packageRoot / "Manifest" / "MetaCore.release.json", &manifestIssue);
if (!manifest) {
issues.push_back(std::move(manifestIssue));
return issues;
}
for (const auto& entry : manifest->Files) {
if (!IsSafeRelativePath(entry.Path)) {
issues.push_back({MetaCoreBuildErrorCode::UnsafePath, "Manifest contains an unsafe file path", entry.Path});
continue;
}
const auto absolute = packageRoot / entry.Path;
std::error_code error;
if (!std::filesystem::is_regular_file(absolute, error)) {
if (entry.Required) issues.push_back({MetaCoreBuildErrorCode::RequiredFileMissing, "Required release file is missing", entry.Path, entry.Role, "missing"});
continue;
}
const auto size = std::filesystem::file_size(absolute, error);
if (error || size != entry.Size) {
issues.push_back({MetaCoreBuildErrorCode::FileSizeMismatch, "Release file size does not match manifest", entry.Path, std::to_string(entry.Size), error ? "unreadable" : std::to_string(size)});
continue;
}
const auto hash = MetaCoreSha256File(absolute);
if (!hash || *hash != entry.Sha256) {
issues.push_back({MetaCoreBuildErrorCode::FileHashMismatch, "Release file SHA-256 does not match manifest", entry.Path, entry.Sha256, hash.value_or("unreadable")});
}
}
MetaCoreBuildIssue deliveryIssue;
const auto delivery = MetaCoreReadRuntimeDeliveryDocument(packageRoot / "Project" / "MetaCore.runtimeproject", &deliveryIssue);
if (!delivery) issues.push_back(std::move(deliveryIssue));
else if (delivery->BuildId != manifest->BuildId) issues.push_back({MetaCoreBuildErrorCode::RuntimeAbiIncompatible, "Runtime project and release manifest Build IDs differ", {}, manifest->BuildId, delivery->BuildId});
return issues;
}
bool MetaCoreExportDiagnostics(const std::filesystem::path& packageRoot, const std::filesystem::path& outputArchive, MetaCoreBuildIssue* issue) {
const auto manifest = packageRoot / "Manifest" / "MetaCore.release.json";
if (!std::filesystem::is_regular_file(manifest)) {
SetIssue(issue, MetaCoreBuildErrorCode::ManifestUnreadable, "Cannot export diagnostics without a release manifest", manifest);
return false;
}
const auto temporary = std::filesystem::temp_directory_path() / ("MetaCoreDiagnostics-" + MakeBuildId());
std::error_code error;
std::filesystem::create_directories(temporary / "Manifest", error);
std::filesystem::copy_file(manifest, temporary / "Manifest" / manifest.filename(), std::filesystem::copy_options::overwrite_existing, error);
if (std::filesystem::is_directory(packageRoot / "Diagnostics") &&
!CopyTree(packageRoot / "Diagnostics", temporary / "Diagnostics")) {
SetIssue(issue, MetaCoreBuildErrorCode::PackageFailed, "Failed to collect diagnostics directory", packageRoot / "Diagnostics");
std::filesystem::remove_all(temporary, error);
return false;
}
const auto runtimeDiagnostics = packageRoot / "Content" / "Runtime" / "Diagnostics.mcruntimestate";
if (std::filesystem::is_regular_file(runtimeDiagnostics)) {
std::filesystem::create_directories(temporary / "Runtime", error);
std::filesystem::copy_file(runtimeDiagnostics, temporary / "Runtime" / runtimeDiagnostics.filename(), std::filesystem::copy_options::overwrite_existing, error);
}
const auto validation = MetaCoreValidateReleasePackage(packageRoot);
json report = json::array();
for (const auto& value : validation) report.push_back({{"code", MetaCoreBuildErrorCodeName(value.Code)}, {"message", value.Message}, {"path", value.Path.generic_string()}});
if (!WriteJson(temporary / "validation.json", report)) {
SetIssue(issue, MetaCoreBuildErrorCode::PackageFailed, "Failed to write diagnostics validation report", temporary);
std::filesystem::remove_all(temporary, error);
return false;
}
std::ofstream systemInfo(temporary / "system.txt");
#if defined(__linux__)
struct utsname systemName{};
systemInfo << "platform=Linux\n";
if (uname(&systemName) == 0) {
systemInfo << "kernel=" << systemName.release << "\narchitecture=" << systemName.machine << '\n';
}
std::ifstream osRelease("/etc/os-release");
systemInfo << osRelease.rdbuf();
std::error_code drmError;
if (std::filesystem::is_directory("/sys/class/drm", drmError)) {
for (const auto& entry : std::filesystem::directory_iterator("/sys/class/drm", drmError)) {
if (entry.path().filename().string().rfind("card", 0) == 0)
systemInfo << "drm_device=" << entry.path().filename().string() << '\n';
}
}
#else
systemInfo << "platform=unknown\n";
#endif
systemInfo << "runtime_abi=" << GMetaCoreRuntimeAbiMajor << '.' << GMetaCoreRuntimeAbiMinor << '\n';
const bool archived = CreateArchive(outputArchive, temporary);
std::filesystem::remove_all(temporary, error);
if (!archived) SetIssue(issue, MetaCoreBuildErrorCode::PackageFailed, "Failed to create diagnostics archive", outputArchive);
return archived;
}
void MetaCoreBuildPipeline::RequestCancel() { CancelRequested_.store(true); }
MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& request, MetaCoreBuildProgressCallback progress) {
CancelRequested_.store(false);
MetaCoreBuildResult result;
auto fail = [&](MetaCoreBuildErrorCode code, const std::string& message, const std::filesystem::path& path = {}) {
result.Issues.push_back({code, message, path});
return result;
};
auto report = [&](MetaCoreBuildStage stage, std::string_view message) { if (progress) progress(stage, message); };
auto cancelled = [&]() { return CancelRequested_.load(); };
if (request.ProjectRoot.empty() || request.ProfilePath.empty() || request.EngineBuildRoot.empty())
return fail(MetaCoreBuildErrorCode::InvalidArguments, "Project, profile, and engine build paths are required");
MetaCoreBuildIssue profileIssue;
const auto profile = MetaCoreReadBuildProfile(request.ProfilePath, &profileIssue);
if (!profile) { result.Issues.push_back(std::move(profileIssue)); return result; }
if (profile->TargetPlatform != "Linux-x86_64" || profile->GraphicsBackend != "OpenGL")
return fail(MetaCoreBuildErrorCode::UnsupportedPlatform, "Only Linux-x86_64 with OpenGL is supported by this release backend", request.ProfilePath);
const auto projectFile = MetaCoreGetProjectFilePath(request.ProjectRoot);
const auto project = MetaCoreReadProjectFile(projectFile);
if (!project) return fail(MetaCoreBuildErrorCode::ProjectUnreadable, "Project descriptor is missing or invalid", projectFile);
if (!IsSafeIdentifier(project->Name) || !IsSafeIdentifier(project->Version))
return fail(MetaCoreBuildErrorCode::UnsafePath, "Project name or version is unsafe for a release path", projectFile);
report(MetaCoreBuildStage::Build, "Building MetaCorePlayer");
if (!request.SkipBuild) {
const std::string command = "cmake --build " + ShellQuote(request.EngineBuildRoot) + " --target MetaCorePlayer --config " + CMakeConfigurationName(profile->Configuration);
if (!RunCommand(command)) return fail(MetaCoreBuildErrorCode::BuildFailed, "MetaCorePlayer build failed", request.EngineBuildRoot);
}
if (cancelled()) return fail(MetaCoreBuildErrorCode::Cancelled, "Build cancelled");
const auto player = FindPlayer(request.EngineBuildRoot, profile->Configuration);
if (player.empty()) return fail(MetaCoreBuildErrorCode::BuildFailed, "MetaCorePlayer executable was not found", request.EngineBuildRoot);
result.BuildId = MakeBuildId();
const auto sourceRoot = FindSourceRoot(request.EngineBuildRoot);
const auto outputRoot = request.ProjectRoot / profile->OutputDirectory / profile->TargetPlatform / project->Name / profile->Name;
const std::string packageName = project->Name + "-" + project->Version + "-" + result.BuildId;
const auto staging = outputRoot / (".staging-" + result.BuildId);
result.PackageDirectory = outputRoot / packageName;
result.ArchivePath = outputRoot / (packageName + ".tar.gz");
result.SymbolsArchivePath = outputRoot / (packageName + ".symbols.tar.gz");
std::error_code error;
if (request.Clean && std::filesystem::is_directory(outputRoot, error)) {
for (const auto& entry : std::filesystem::directory_iterator(outputRoot, error))
if (entry.path().filename().string().rfind(".staging-", 0) == 0) std::filesystem::remove_all(entry.path(), error);
}
std::filesystem::remove_all(staging, error);
std::filesystem::create_directories(staging / "Content" / "Scenes", error);
std::filesystem::create_directories(staging / "Content" / "Assets", error);
std::filesystem::create_directories(staging / "Content" / "Ui", error);
std::filesystem::create_directories(staging / "Content" / "Runtime", error);
std::filesystem::create_directories(staging / "Engine" / "Materials", error);
std::filesystem::create_directories(staging / "Engine" / "Fonts", error);
std::filesystem::create_directories(staging / "Diagnostics" / "Logs", error);
if (error) return fail(MetaCoreBuildErrorCode::StageFailed, "Failed to create staging directory", staging);
report(MetaCoreBuildStage::Cook, "Cooking startup scene and project content");
std::filesystem::path startupSource = profile->StartupScene.empty() ? project->StartupScenePath : profile->StartupScene;
if (!IsSafeRelativePath(startupSource)) return fail(MetaCoreBuildErrorCode::UnsafePath, "Startup scene path is unsafe", startupSource);
const auto absoluteStartup = request.ProjectRoot / startupSource;
std::filesystem::path cookedSceneName = startupSource.filename();
if (cookedSceneName.extension() == ".json") cookedSceneName.replace_extension("");
const auto cookedScene = staging / "Content" / "Scenes" / cookedSceneName;
if (startupSource.extension() == ".json") {
const auto registry = MetaCoreBuildScenePackageTypeRegistry();
const auto scene = MetaCoreSceneSerializer::LoadSceneFromJson(absoluteStartup, registry);
if (!scene || !MetaCoreWriteScenePackage(cookedScene, *scene)) return fail(MetaCoreBuildErrorCode::CookFailed, "Failed to cook startup scene", absoluteStartup);
} else {
std::filesystem::copy_file(absoluteStartup, cookedScene, std::filesystem::copy_options::overwrite_existing, error);
if (error) return fail(MetaCoreBuildErrorCode::CookFailed, "Failed to copy startup scene", absoluteStartup);
}
const auto currentCookRoot = request.ProjectRoot / "Library" / "Cooked" / profile->TargetPlatform / profile->Name;
const auto editorCookRoot = request.ProjectRoot / "Library" / "Cooked" / profile->TargetPlatform / "Editor";
const auto legacyCookRoot = request.ProjectRoot / "Library" / "Cooked" / "Windows";
bool copiedCooked = false;
if (std::filesystem::is_directory(currentCookRoot)) copiedCooked = CopyTree(currentCookRoot, staging / "Content" / "Assets");
else if (std::filesystem::is_directory(editorCookRoot)) copiedCooked = CopyTree(editorCookRoot, staging / "Content" / "Assets");
else if (std::filesystem::is_directory(legacyCookRoot)) copiedCooked = CopyTree(legacyCookRoot, staging / "Content" / "Assets");
if ((profile->ResourcePolicy == MetaCoreBuildResourcePolicy::AllProjectContent || !copiedCooked) && std::filesystem::is_directory(request.ProjectRoot / "Assets"))
if (!CopyTree(request.ProjectRoot / "Assets", staging / "Content" / "Assets")) return fail(MetaCoreBuildErrorCode::CookFailed, "Failed to collect project assets", request.ProjectRoot / "Assets");
if (std::filesystem::is_directory(request.ProjectRoot / project->UiDirectory) &&
!CopyTree(request.ProjectRoot / project->UiDirectory, staging / "Content" / "Ui"))
return fail(MetaCoreBuildErrorCode::CookFailed, "Failed to collect project UI", request.ProjectRoot / project->UiDirectory);
if (std::filesystem::is_directory(request.ProjectRoot / project->RuntimeDirectory) &&
!CopyTree(request.ProjectRoot / project->RuntimeDirectory, staging / "Content" / "Runtime"))
return fail(MetaCoreBuildErrorCode::CookFailed, "Failed to collect project runtime configuration", request.ProjectRoot / project->RuntimeDirectory);
if (cancelled()) { std::filesystem::remove_all(staging, error); return fail(MetaCoreBuildErrorCode::Cancelled, "Build cancelled"); }
report(MetaCoreBuildStage::Stage, "Staging executable and runtime dependencies");
std::filesystem::copy_file(player, staging / player.filename(), std::filesystem::copy_options::overwrite_existing, error);
if (error) return fail(MetaCoreBuildErrorCode::StageFailed, "Failed to stage MetaCorePlayer", player);
std::filesystem::permissions(staging / player.filename(), std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec, std::filesystem::perm_options::add, error);
for (const char* material : {"uiBlit.filamat", "rml_ui.filamat"}) {
const auto source = request.EngineBuildRoot / material;
if (!std::filesystem::is_regular_file(source)) return fail(MetaCoreBuildErrorCode::StageFailed, "Required engine material is missing", source);
std::filesystem::copy_file(source, staging / "Engine" / "Materials" / material, std::filesystem::copy_options::overwrite_existing, error);
}
const std::array fontCandidates = {
sourceRoot / "third_party" / "filament_installed" / "bin" / "assets" / "fonts" / "Roboto-Medium.ttf",
sourceRoot / "third_party" / "filament_installed.before-linux-sdk" / "bin" / "assets" / "fonts" / "Roboto-Medium.ttf"
};
bool fontCopied = false;
for (const auto& font : fontCandidates) if (!fontCopied && std::filesystem::is_regular_file(font)) {
std::filesystem::copy_file(font, staging / "Engine" / "Fonts" / font.filename(), std::filesystem::copy_options::overwrite_existing, error);
fontCopied = !error;
}
if (!fontCopied) return fail(MetaCoreBuildErrorCode::StageFailed, "Required runtime font is missing", sourceRoot);
if (!CopyLinuxRuntimeLibraries(player, staging / "lib")) return fail(MetaCoreBuildErrorCode::DependencyMissing, "Failed to inspect or stage Linux C++ runtime libraries", player);
const std::array crashpadCandidates = {
sourceRoot / "vcpkg_installed" / "x64-linux-clang-libcxx" / "tools" / "crashpad" / "crashpad_handler",
request.EngineBuildRoot / "vcpkg_installed" / "x64-linux-clang-libcxx" / "tools" / "crashpad" / "crashpad_handler"
};
bool crashpadHandlerCopied = false;
for (const auto& handler : crashpadCandidates) {
if (!std::filesystem::is_regular_file(handler)) continue;
std::filesystem::create_directories(staging / "Crash", error);
std::filesystem::copy_file(handler, staging / "Crash" / "crashpad_handler", std::filesystem::copy_options::overwrite_existing, error);
std::filesystem::permissions(staging / "Crash" / "crashpad_handler", std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec, std::filesystem::perm_options::add, error);
crashpadHandlerCopied = !error;
break;
}
#if defined(METACORE_HAS_CRASHPAD) && METACORE_HAS_CRASHPAD
if (!crashpadHandlerCopied) return fail(MetaCoreBuildErrorCode::DependencyMissing, "Crashpad is linked but crashpad_handler was not found", sourceRoot);
#else
(void)crashpadHandlerCopied;
#endif
MetaCoreRuntimeDeliveryDocument delivery;
delivery.BuildId = result.BuildId;
delivery.StartupScene = std::filesystem::path("Content") / "Scenes" / cookedSceneName;
if (!MetaCoreWriteRuntimeDeliveryDocument(staging / "Project" / "MetaCore.runtimeproject", delivery))
return fail(MetaCoreBuildErrorCode::StageFailed, "Failed to write runtime project document", staging);
const auto symbolsRoot = outputRoot / (".symbols-" + result.BuildId);
std::filesystem::remove_all(symbolsRoot, error);
if (profile->Configuration != MetaCoreBuildConfiguration::Debug) {
std::filesystem::create_directories(symbolsRoot, error);
const auto debugFile = symbolsRoot / "MetaCorePlayer.debug";
const std::string keepDebug = "objcopy --only-keep-debug " + ShellQuote(staging / player.filename()) + " " + ShellQuote(debugFile);
if (!RunCommand(keepDebug)) return fail(MetaCoreBuildErrorCode::PackageFailed, "Failed to create detached debug symbols", staging / player.filename());
if (profile->Configuration == MetaCoreBuildConfiguration::Release) {
if (!RunCommand("strip --strip-unneeded " + ShellQuote(staging / player.filename())) ||
!RunCommand("objcopy --add-gnu-debuglink=" + ShellQuote(debugFile) + " " + ShellQuote(staging / player.filename())))
return fail(MetaCoreBuildErrorCode::PackageFailed, "Failed to strip or link release symbols", staging / player.filename());
}
}
report(MetaCoreBuildStage::Package, "Writing release manifest and archives");
MetaCoreReleaseManifestDocument manifest;
manifest.ProjectName = project->Name; manifest.ProjectVersion = project->Version; manifest.BuildId = result.BuildId;
manifest.GitCommit = DetectGitCommit(sourceRoot); manifest.TargetPlatform = profile->TargetPlatform;
manifest.Configuration = ConfigurationName(profile->Configuration); manifest.GraphicsBackend = profile->GraphicsBackend;
manifest.SystemDependencies = {"Ubuntu 22.04 x86_64", "glibc >= 2.35", "OpenGL/GLX", "X11"};
manifest.Files = CollectManifestFiles(staging);
if (!MetaCoreWriteReleaseManifest(staging / "Manifest" / "MetaCore.release.json", manifest))
return fail(MetaCoreBuildErrorCode::PackageFailed, "Failed to write release manifest", staging);
std::filesystem::rename(staging, result.PackageDirectory, error);
if (error) return fail(MetaCoreBuildErrorCode::PackageFailed, "Failed to commit staging directory", result.PackageDirectory);
if (!CreateArchive(result.ArchivePath, result.PackageDirectory)) return fail(MetaCoreBuildErrorCode::PackageFailed, "Failed to create release archive", result.ArchivePath);
if (profile->Configuration != MetaCoreBuildConfiguration::Debug) {
if (!CreateArchive(result.SymbolsArchivePath, symbolsRoot)) return fail(MetaCoreBuildErrorCode::PackageFailed, "Failed to create symbols archive", result.SymbolsArchivePath);
std::filesystem::remove_all(symbolsRoot, error);
} else result.SymbolsArchivePath.clear();
report(MetaCoreBuildStage::Validate, "Validating release package");
result.Issues = MetaCoreValidateReleasePackage(result.PackageDirectory);
result.Success = result.Issues.empty();
return result;
}
} // namespace MetaCore

View File

@ -0,0 +1,45 @@
#include "MetaCoreDelivery/MetaCoreCrashReporter.h"
#if defined(METACORE_HAS_CRASHPAD) && METACORE_HAS_CRASHPAD
#include <client/crashpad_client.h>
#include <client/crash_report_database.h>
#include <base/files/file_path.h>
#endif
#include <map>
#include <string>
#include <vector>
namespace MetaCore {
bool MetaCoreInitializeCrashReporter(
const std::filesystem::path& executableRoot,
const std::filesystem::path& diagnosticsRoot,
std::string_view buildId
) {
#if defined(METACORE_HAS_CRASHPAD) && METACORE_HAS_CRASHPAD
const std::filesystem::path handler = executableRoot / "Crash" / "crashpad_handler";
const std::filesystem::path database = diagnosticsRoot / "Crashpad";
const std::filesystem::path metrics = diagnosticsRoot / "CrashpadMetrics";
std::error_code error;
std::filesystem::create_directories(database, error);
std::filesystem::create_directories(metrics, error);
if (error || !std::filesystem::is_regular_file(handler)) return false;
std::map<std::string, std::string> annotations = {
{"product", "MetaCorePlayer"}, {"version", "1.0.0"}, {"build_id", std::string(buildId)}
};
std::vector<std::string> arguments = {"--no-rate-limit"};
static crashpad::CrashpadClient client;
return client.StartHandler(
base::FilePath(handler.string()), base::FilePath(database.string()), base::FilePath(metrics.string()),
"", annotations, arguments, false, false
);
#else
(void)executableRoot;
(void)diagnosticsRoot;
(void)buildId;
return false;
#endif
}
} // namespace MetaCore

View File

@ -0,0 +1,162 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <optional>
#include <string>
#include <vector>
namespace MetaCore {
inline constexpr std::uint32_t GMetaCoreReleaseManifestVersion = 1U;
inline constexpr std::uint32_t GMetaCoreRuntimeDeliveryVersion = 1U;
inline constexpr std::uint32_t GMetaCoreRuntimeAbiMajor = 1U;
inline constexpr std::uint32_t GMetaCoreRuntimeAbiMinor = 0U;
enum class MetaCoreBuildConfiguration { Debug, Development, Release };
enum class MetaCoreBuildResourcePolicy { ReferencedOnly, AllProjectContent };
enum class MetaCoreBuildStage { Build, Cook, Stage, Package, Validate };
enum class MetaCoreBuildErrorCode {
None,
InvalidArguments,
InvalidProfile,
UnsupportedPlatform,
UnsafePath,
ProjectUnreadable,
BuildFailed,
CookFailed,
StageFailed,
PackageFailed,
ManifestUnreadable,
ManifestVersionUnsupported,
RuntimeAbiIncompatible,
RequiredFileMissing,
FileSizeMismatch,
FileHashMismatch,
DependencyMissing,
Cancelled
};
struct MetaCoreBuildProfile {
std::uint32_t FormatVersion = 1U;
std::string Name = "LinuxDevelopment";
std::string TargetPlatform = "Linux-x86_64";
MetaCoreBuildConfiguration Configuration = MetaCoreBuildConfiguration::Development;
std::string GraphicsBackend = "OpenGL";
std::filesystem::path StartupScene{};
std::filesystem::path OutputDirectory = "Build";
MetaCoreBuildResourcePolicy ResourcePolicy = MetaCoreBuildResourcePolicy::ReferencedOnly;
};
struct MetaCoreRuntimeDeliveryDocument {
std::uint32_t FormatVersion = GMetaCoreRuntimeDeliveryVersion;
std::uint32_t RuntimeAbiMajor = GMetaCoreRuntimeAbiMajor;
std::uint32_t RuntimeAbiMinor = GMetaCoreRuntimeAbiMinor;
std::string BuildId{};
std::filesystem::path ContentRoot = "Content";
std::filesystem::path StartupScene{};
std::filesystem::path RuntimeConfig = "Content/Runtime/ProjectRuntime.mcruntimecfg";
std::filesystem::path UiManifest = "Content/Runtime/Ui.mcruntime";
std::filesystem::path ReleaseManifest = "Manifest/MetaCore.release.json";
};
struct MetaCoreReleaseFileEntry {
std::filesystem::path Path{};
std::string Role{};
std::uint64_t Size = 0U;
std::string Sha256{};
bool Required = true;
};
struct MetaCoreReleaseManifestDocument {
std::uint32_t FormatVersion = GMetaCoreReleaseManifestVersion;
std::uint32_t RuntimeAbiMajor = GMetaCoreRuntimeAbiMajor;
std::uint32_t RuntimeAbiMinor = GMetaCoreRuntimeAbiMinor;
std::uint32_t PackageFormatVersion = 1U;
std::string EngineVersion = "1.0.0";
std::string ProjectName{};
std::string ProjectVersion{};
std::string BuildId{};
std::string GitCommit{};
std::string TargetPlatform{};
std::string Configuration{};
std::string GraphicsBackend{};
std::vector<std::string> SystemDependencies{};
std::vector<MetaCoreReleaseFileEntry> Files{};
};
struct MetaCoreBuildIssue {
MetaCoreBuildErrorCode Code = MetaCoreBuildErrorCode::None;
std::string Message{};
std::filesystem::path Path{};
std::string Expected{};
std::string Actual{};
};
struct MetaCoreBuildRequest {
std::filesystem::path ProjectRoot{};
std::filesystem::path ProfilePath{};
std::filesystem::path EngineBuildRoot{};
bool Clean = false;
bool SkipBuild = false;
};
struct MetaCoreBuildResult {
bool Success = false;
std::string BuildId{};
std::filesystem::path PackageDirectory{};
std::filesystem::path ArchivePath{};
std::filesystem::path SymbolsArchivePath{};
std::vector<MetaCoreBuildIssue> Issues{};
};
using MetaCoreBuildProgressCallback = std::function<void(MetaCoreBuildStage, std::string_view)>;
[[nodiscard]] const char* MetaCoreBuildErrorCodeName(MetaCoreBuildErrorCode code);
[[nodiscard]] const char* MetaCoreBuildStageName(MetaCoreBuildStage stage);
[[nodiscard]] std::optional<MetaCoreBuildProfile> MetaCoreReadBuildProfile(
const std::filesystem::path& path,
MetaCoreBuildIssue* issue = nullptr
);
[[nodiscard]] bool MetaCoreWriteBuildProfile(const std::filesystem::path& path, const MetaCoreBuildProfile& profile);
[[nodiscard]] std::optional<MetaCoreRuntimeDeliveryDocument> MetaCoreReadRuntimeDeliveryDocument(
const std::filesystem::path& path,
MetaCoreBuildIssue* issue = nullptr
);
[[nodiscard]] bool MetaCoreWriteRuntimeDeliveryDocument(
const std::filesystem::path& path,
const MetaCoreRuntimeDeliveryDocument& document
);
[[nodiscard]] std::optional<MetaCoreReleaseManifestDocument> MetaCoreReadReleaseManifest(
const std::filesystem::path& path,
MetaCoreBuildIssue* issue = nullptr
);
[[nodiscard]] bool MetaCoreWriteReleaseManifest(
const std::filesystem::path& path,
const MetaCoreReleaseManifestDocument& document
);
[[nodiscard]] std::vector<MetaCoreBuildIssue> MetaCoreValidateReleasePackage(
const std::filesystem::path& packageRoot
);
[[nodiscard]] bool MetaCoreExportDiagnostics(
const std::filesystem::path& packageRoot,
const std::filesystem::path& outputArchive,
MetaCoreBuildIssue* issue = nullptr
);
class MetaCoreBuildPipeline {
public:
void RequestCancel();
[[nodiscard]] MetaCoreBuildResult Run(
const MetaCoreBuildRequest& request,
MetaCoreBuildProgressCallback progress = {}
);
private:
std::atomic_bool CancelRequested_{false};
};
} // namespace MetaCore

View File

@ -0,0 +1,15 @@
#pragma once
#include <filesystem>
#include <string_view>
namespace MetaCore {
// Starts the local-only crash handler. No upload URL is configured.
[[nodiscard]] bool MetaCoreInitializeCrashReporter(
const std::filesystem::path& executableRoot,
const std::filesystem::path& diagnosticsRoot,
std::string_view buildId
);
} // namespace MetaCore

View File

@ -6442,7 +6442,12 @@ bool MetaCoreBuiltinAssetDatabaseService::SetStartupScenePath(const std::filesys
}
[[nodiscard]] std::filesystem::path MetaCoreBuildCookManifestPath(const MetaCoreProjectDescriptor& project) {
return project.LibraryPath / "Cooked" / "Windows" / "CookManifest.bin";
#if defined(_WIN32)
constexpr std::string_view platform = "Windows-x86_64";
#else
constexpr std::string_view platform = "Linux-x86_64";
#endif
return project.LibraryPath / "Cooked" / platform / "Editor" / "CookManifest.bin";
}
[[nodiscard]] bool MetaCoreCopyDirectoryTree(
@ -6658,8 +6663,13 @@ bool MetaCoreBuiltinCookService::CookAsset(const MetaCoreAssetGuid& assetGuid) {
return true;
}
#if defined(_WIN32)
constexpr std::string_view cookPlatform = "Windows-x86_64";
#else
constexpr std::string_view cookPlatform = "Linux-x86_64";
#endif
const std::filesystem::path relativeCookedPath =
std::filesystem::path("Library") / "Cooked" / "Windows" /
std::filesystem::path("Library") / "Cooked" / cookPlatform / "Editor" /
(assetGuid.ToString() + "_" + MetaCoreFormatHex64(cookedKey) + ".mccooked");
const std::filesystem::path absoluteCookedPath = AssetDatabaseService_->GetProjectDescriptor().RootPath / relativeCookedPath;
std::filesystem::create_directories(absoluteCookedPath.parent_path());

View File

@ -7,6 +7,7 @@
#include "MetaCoreEditor/MetaCoreEditorContext.h"
#include "MetaCoreEditor/MetaCoreEditorAssetTypes.h"
#include "MetaCoreEditor/MetaCoreEditorServices.h"
#include "MetaCoreDelivery/MetaCoreBuildPipeline.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include "MetaCoreEditorCameraController.h"
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
@ -46,6 +47,8 @@
#include <unordered_set>
#include <vector>
#include <fstream>
#include <future>
#include <mutex>
#include <nlohmann/json.hpp>
#include <glm/vec2.hpp>
@ -2575,7 +2578,9 @@ public:
MetaCoreLaunchPlayer(editorContext, *assetDatabaseService, *scenePersistenceService);
}
ImGui::Separator();
ImGui::MenuItem("构建设置", nullptr, false, false);
if (ImGui::MenuItem("构建设置")) {
editorContext.GetModuleRegistry().AccessPanelOpenState("BuildSettings") = true;
}
ImGui::MenuItem("偏好设置", nullptr, false, false);
ImGui::Separator();
if (ImGui::MenuItem("退出")) {
@ -9462,6 +9467,120 @@ private:
std::array<char, 256> NameBuffer_{};
};
class MetaCoreBuildSettingsPanelProvider final : public MetaCoreIEditorPanelProvider {
public:
std::string GetPanelId() const override { return "BuildSettings"; }
std::string GetPanelTitle() const override { return "构建设置"; }
bool IsOpenByDefault() const override { return false; }
void DrawPanel(MetaCoreEditorContext& editorContext) override {
const auto assetDatabase = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
if (assetDatabase == nullptr || !assetDatabase->HasProject()) {
ImGui::TextUnformatted("请先打开项目。");
return;
}
const auto& project = assetDatabase->GetProjectDescriptor();
const std::filesystem::path defaultProfile = project.RootPath / "BuildProfiles" / "LinuxDevelopment.mcbuildprofile.json";
if (LoadedProjectRoot_ != project.RootPath) {
LoadedProjectRoot_ = project.RootPath;
ProfilePath_ = defaultProfile;
EngineBuildRoot_ = std::filesystem::current_path();
MetaCoreBuildIssue issue;
if (const auto loaded = MetaCoreReadBuildProfile(ProfilePath_, &issue)) Profile_ = *loaded;
else Profile_ = MetaCoreBuildProfile{};
}
ImGui::Text("Profile: %s", ProfilePath_.generic_string().c_str());
ImGui::Text("平台: %s", Profile_.TargetPlatform.c_str());
ImGui::Text("图形后端: %s", Profile_.GraphicsBackend.c_str());
int configuration = static_cast<int>(Profile_.Configuration);
const char* configurations[] = {"Debug", "Development", "Release"};
if (ImGui::Combo("配置", &configuration, configurations, 3)) Profile_.Configuration = static_cast<MetaCoreBuildConfiguration>(configuration);
bool allContent = Profile_.ResourcePolicy == MetaCoreBuildResourcePolicy::AllProjectContent;
if (ImGui::Checkbox("包含全部项目内容", &allContent))
Profile_.ResourcePolicy = allContent ? MetaCoreBuildResourcePolicy::AllProjectContent : MetaCoreBuildResourcePolicy::ReferencedOnly;
ImGui::Text("启动场景: %s", Profile_.StartupScene.generic_string().c_str());
ImGui::Text("引擎构建目录: %s", EngineBuildRoot_.generic_string().c_str());
if (ImGui::Button("保存 Profile")) {
if (MetaCoreWriteBuildProfile(ProfilePath_, Profile_))
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Build", "Build Profile 已保存");
else editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Build", "Build Profile 保存失败");
}
ImGui::SameLine();
const bool running = Future_.valid() && Future_.wait_for(std::chrono::seconds(0)) != std::future_status::ready;
if (running) ImGui::BeginDisabled();
if (ImGui::Button("Build / Package")) {
(void)MetaCoreWriteBuildProfile(ProfilePath_, Profile_);
Pipeline_ = std::make_shared<MetaCoreBuildPipeline>();
MetaCoreBuildRequest request;
request.ProjectRoot = project.RootPath;
request.ProfilePath = ProfilePath_;
request.EngineBuildRoot = EngineBuildRoot_;
request.Clean = true;
const auto pipeline = Pipeline_;
Future_ = std::async(std::launch::async, [this, pipeline, request]() {
return pipeline->Run(request, [this](MetaCoreBuildStage stage, std::string_view message) {
std::scoped_lock lock(ProgressMutex_);
Progress_ = std::string(MetaCoreBuildStageName(stage)) + ": " + std::string(message);
});
});
}
if (running) ImGui::EndDisabled();
if (running) {
ImGui::SameLine();
if (ImGui::Button("取消")) Pipeline_->RequestCancel();
std::string progress;
{
std::scoped_lock lock(ProgressMutex_);
progress = Progress_;
}
ImGui::TextUnformatted(progress.empty() ? "构建进行中……" : progress.c_str());
} else if (Future_.valid()) {
LastResult_ = Future_.get();
Pipeline_.reset();
editorContext.AddConsoleMessage(
LastResult_.Success ? MetaCoreLogLevel::Info : MetaCoreLogLevel::Error,
"Build",
LastResult_.Success ? "发布包构建与校验成功" : "发布包构建失败"
);
}
if (!LastResult_.PackageDirectory.empty()) {
ImGui::Separator();
ImGui::TextWrapped("输出: %s", LastResult_.PackageDirectory.generic_string().c_str());
if (ImGui::Button("Validate")) {
LastResult_.Issues = MetaCoreValidateReleasePackage(LastResult_.PackageDirectory);
LastResult_.Success = LastResult_.Issues.empty();
}
ImGui::SameLine();
if (ImGui::Button("打开输出目录")) {
#if defined(_WIN32)
ShellExecuteW(nullptr, L"open", LastResult_.PackageDirectory.wstring().c_str(), nullptr, nullptr, SW_SHOWNORMAL);
#else
const std::string outputPath = LastResult_.PackageDirectory.string();
if (outputPath.find('\'') == std::string::npos) {
(void)std::system(("xdg-open '" + outputPath + "' >/dev/null 2>&1 &").c_str());
}
#endif
}
for (const auto& issue : LastResult_.Issues) {
ImGui::TextWrapped("%s: %s (%s)", MetaCoreBuildErrorCodeName(issue.Code), issue.Message.c_str(), issue.Path.generic_string().c_str());
}
}
}
private:
std::filesystem::path LoadedProjectRoot_{};
std::filesystem::path ProfilePath_{};
std::filesystem::path EngineBuildRoot_{};
MetaCoreBuildProfile Profile_{};
std::shared_ptr<MetaCoreBuildPipeline> Pipeline_{};
MetaCoreBuildResult LastResult_{};
std::mutex ProgressMutex_{};
std::string Progress_{};
std::future<MetaCoreBuildResult> Future_{};
};
class MetaCoreBuiltinEditorViewsModule final : public MetaCoreIModule {
public:
std::string GetModuleName() const override { return "MetaCoreBuiltinEditorViewsModule"; }
@ -9479,6 +9598,7 @@ public:
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreInspectorPanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreProjectPanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreConsolePanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreBuildSettingsPanelProvider>());
moduleRegistry.RegisterInspectorDrawer(std::make_unique<MetaCoreTransformInspectorDrawer>());
}

View File

@ -59,6 +59,27 @@ MetaCoreAssetDependencyGraph::AssetGuidSet MetaCoreAssetDependencyGraph::GetDepe
return iterator == Dependents_.end() ? AssetGuidSet{} : iterator->second;
}
MetaCoreAssetDependencyGraph::AssetGuidSet MetaCoreAssetDependencyGraph::GetDependencyClosure(
const std::vector<MetaCoreAssetGuid>& roots
) const {
std::scoped_lock lock(Mutex_);
AssetGuidSet closure;
std::vector<MetaCoreAssetGuid> pending;
for (const auto& root : roots) {
if (root.IsValid() && closure.insert(root).second) pending.push_back(root);
}
while (!pending.empty()) {
const MetaCoreAssetGuid current = pending.back();
pending.pop_back();
const auto dependencies = Dependencies_.find(current);
if (dependencies == Dependencies_.end()) continue;
for (const auto& dependency : dependencies->second) {
if (closure.insert(dependency).second) pending.push_back(dependency);
}
}
return closure;
}
void MetaCoreAssetDependencyGraph::RemoveAsset(const MetaCoreAssetGuid& assetGuid) {
if (!assetGuid.IsValid()) {
return;

View File

@ -2,6 +2,9 @@
#include <array>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <vector>
namespace MetaCore {
@ -10,6 +13,21 @@ namespace {
constexpr std::uint64_t GMetaCoreFnvOffsetBasis = 1469598103934665603ULL;
constexpr std::uint64_t GMetaCoreFnvPrime = 1099511628211ULL;
constexpr std::array<std::uint32_t, 64> GMetaCoreSha256RoundConstants = {
0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, 0x3956c25bU, 0x59f111f1U, 0x923f82a4U, 0xab1c5ed5U,
0xd807aa98U, 0x12835b01U, 0x243185beU, 0x550c7dc3U, 0x72be5d74U, 0x80deb1feU, 0x9bdc06a7U, 0xc19bf174U,
0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU, 0x2de92c6fU, 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU,
0x983e5152U, 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U, 0xc6e00bf3U, 0xd5a79147U, 0x06ca6351U, 0x14292967U,
0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU, 0x53380d13U, 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U,
0xa2bfe8a1U, 0xa81a664bU, 0xc24b8b70U, 0xc76c51a3U, 0xd192e819U, 0xd6990624U, 0xf40e3585U, 0x106aa070U,
0x19a4c116U, 0x1e376c08U, 0x2748774cU, 0x34b0bcb5U, 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU, 0x682e6ff3U,
0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, 0xc67178f2U
};
[[nodiscard]] constexpr std::uint32_t MetaCoreRotateRight(std::uint32_t value, std::uint32_t count) {
return (value >> count) | (value << (32U - count));
}
} // namespace
std::uint64_t MetaCoreHashBytes(std::span<const std::byte> data) {
@ -56,4 +74,77 @@ std::uint64_t MetaCoreHashCombine(std::uint64_t seed, std::uint64_t value) {
return seed;
}
std::string MetaCoreSha256Bytes(std::span<const std::byte> data) {
std::vector<std::uint8_t> message;
message.reserve(data.size() + 72U);
for (const std::byte value : data) {
message.push_back(std::to_integer<std::uint8_t>(value));
}
const std::uint64_t bitLength = static_cast<std::uint64_t>(message.size()) * 8ULL;
message.push_back(0x80U);
while ((message.size() % 64U) != 56U) {
message.push_back(0U);
}
for (int shift = 56; shift >= 0; shift -= 8) {
message.push_back(static_cast<std::uint8_t>((bitLength >> shift) & 0xffULL));
}
std::array<std::uint32_t, 8> state = {
0x6a09e667U, 0xbb67ae85U, 0x3c6ef372U, 0xa54ff53aU,
0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U
};
for (std::size_t offset = 0; offset < message.size(); offset += 64U) {
std::array<std::uint32_t, 64> words{};
for (std::size_t index = 0; index < 16U; ++index) {
const std::size_t base = offset + index * 4U;
words[index] = (static_cast<std::uint32_t>(message[base]) << 24U) |
(static_cast<std::uint32_t>(message[base + 1U]) << 16U) |
(static_cast<std::uint32_t>(message[base + 2U]) << 8U) |
static_cast<std::uint32_t>(message[base + 3U]);
}
for (std::size_t index = 16U; index < words.size(); ++index) {
const std::uint32_t s0 = MetaCoreRotateRight(words[index - 15U], 7U) ^ MetaCoreRotateRight(words[index - 15U], 18U) ^ (words[index - 15U] >> 3U);
const std::uint32_t s1 = MetaCoreRotateRight(words[index - 2U], 17U) ^ MetaCoreRotateRight(words[index - 2U], 19U) ^ (words[index - 2U] >> 10U);
words[index] = words[index - 16U] + s0 + words[index - 7U] + s1;
}
std::uint32_t a = state[0];
std::uint32_t b = state[1];
std::uint32_t c = state[2];
std::uint32_t d = state[3];
std::uint32_t e = state[4];
std::uint32_t f = state[5];
std::uint32_t g = state[6];
std::uint32_t h = state[7];
for (std::size_t index = 0; index < words.size(); ++index) {
const std::uint32_t sum1 = MetaCoreRotateRight(e, 6U) ^ MetaCoreRotateRight(e, 11U) ^ MetaCoreRotateRight(e, 25U);
const std::uint32_t choice = (e & f) ^ ((~e) & g);
const std::uint32_t temporary1 = h + sum1 + choice + GMetaCoreSha256RoundConstants[index] + words[index];
const std::uint32_t sum0 = MetaCoreRotateRight(a, 2U) ^ MetaCoreRotateRight(a, 13U) ^ MetaCoreRotateRight(a, 22U);
const std::uint32_t majority = (a & b) ^ (a & c) ^ (b & c);
const std::uint32_t temporary2 = sum0 + majority;
h = g; g = f; f = e; e = d + temporary1;
d = c; c = b; b = a; a = temporary1 + temporary2;
}
state[0] += a; state[1] += b; state[2] += c; state[3] += d;
state[4] += e; state[5] += f; state[6] += g; state[7] += h;
}
std::ostringstream output;
output << std::hex << std::setfill('0');
for (const std::uint32_t value : state) {
output << std::setw(8) << value;
}
return output.str();
}
std::optional<std::string> MetaCoreSha256File(const std::filesystem::path& path) {
std::ifstream input(path, std::ios::binary);
if (!input.is_open()) {
return std::nullopt;
}
std::vector<char> contents((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
const auto* bytes = reinterpret_cast<const std::byte*>(contents.data());
return MetaCoreSha256Bytes(std::span(bytes, contents.size()));
}
} // namespace MetaCore

View File

@ -161,13 +161,14 @@ bool MetaCoreCreateProjectSkeleton(
}
std::error_code errorCode;
const std::array<std::filesystem::path, 10> directories = {
const std::array<std::filesystem::path, 11> directories = {
normalizedRoot / "Assets",
normalizedRoot / "Scenes",
normalizedRoot / "Runtime",
normalizedRoot / "Ui",
normalizedRoot / "Library",
normalizedRoot / "Build",
normalizedRoot / "BuildProfiles",
normalizedRoot / "Assets" / "Models",
normalizedRoot / "Assets" / "Materials",
normalizedRoot / "Assets" / "Textures",
@ -220,6 +221,23 @@ input { width: 150dp; padding: 6dp; background-color: #0f1720; color: #ffffff; }
return false;
}
constexpr std::string_view defaultBuildProfile = R"json({
"format_version": 1,
"name": "LinuxDevelopment",
"target_platform": "Linux-x86_64",
"configuration": "Development",
"graphics_backend": "OpenGL",
"startup_scene": "Scenes/Main.mcscene.json",
"output_directory": "Build",
"resource_policy": "ReferencedOnly"
}
)json";
if (!MetaCoreWriteTextFileIfMissing(
normalizedRoot / "BuildProfiles" / "LinuxDevelopment.mcbuildprofile.json",
defaultBuildProfile)) {
return false;
}
MetaCoreProjectFileDocument document;
if (!projectName.empty()) {
document.Name = std::string(projectName);

View File

@ -5,6 +5,7 @@
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace MetaCore {
@ -17,6 +18,7 @@ public:
void SetDependencies(const MetaCoreAssetGuid& assetGuid, const AssetGuidSet& dependencies);
[[nodiscard]] AssetGuidSet GetDependencies(const MetaCoreAssetGuid& assetGuid) const;
[[nodiscard]] AssetGuidSet GetDependents(const MetaCoreAssetGuid& assetGuid) const;
[[nodiscard]] AssetGuidSet GetDependencyClosure(const std::vector<MetaCoreAssetGuid>& roots) const;
void RemoveAsset(const MetaCoreAssetGuid& assetGuid);
void Clear();

View File

@ -6,6 +6,7 @@
#include <optional>
#include <span>
#include <string_view>
#include <string>
namespace MetaCore {
@ -13,6 +14,7 @@ namespace MetaCore {
[[nodiscard]] std::uint64_t MetaCoreHashString(std::string_view value);
[[nodiscard]] std::optional<std::uint64_t> MetaCoreHashFile(const std::filesystem::path& path);
[[nodiscard]] std::uint64_t MetaCoreHashCombine(std::uint64_t seed, std::uint64_t value);
[[nodiscard]] std::string MetaCoreSha256Bytes(std::span<const std::byte> data);
[[nodiscard]] std::optional<std::string> MetaCoreSha256File(const std::filesystem::path& path);
} // namespace MetaCore

View File

@ -45,6 +45,9 @@ MetaCoreImGuiHelper::MetaCoreImGuiHelper(Engine* engine, filament::View* view, c
// 加载我们编译好的材质
std::ifstream file("uiBlit.filamat", std::ios::binary);
if (!file.is_open()) {
file.open("Engine/Materials/uiBlit.filamat", std::ios::binary);
}
if (!file.is_open()) {
file.open("../uiBlit.filamat", std::ios::binary);
}

View File

@ -117,8 +117,10 @@ namespace {
[[nodiscard]] std::vector<char> MetaCoreLoadRuntimeUiMaterialPackage() {
for (const std::filesystem::path& candidate : {
std::filesystem::path("rml_ui.filamat"),
std::filesystem::path("Engine/Materials/rml_ui.filamat"),
std::filesystem::path("../rml_ui.filamat"),
std::filesystem::path("uiBlit.filamat"),
std::filesystem::path("Engine/Materials/uiBlit.filamat"),
std::filesystem::path("../uiBlit.filamat")
}) {
std::ifstream input(candidate, std::ios::binary);

View File

@ -0,0 +1,10 @@
{
"format_version": 1,
"name": "LinuxDevelopment",
"target_platform": "Linux-x86_64",
"configuration": "Development",
"graphics_backend": "OpenGL",
"startup_scene": "Scenes/Main.mcscene.json",
"output_directory": "Build",
"resource_policy": "ReferencedOnly"
}

View File

@ -0,0 +1,9 @@
FROM ubuntu:22.04
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates libgl1 libglx0 libopengl0 libx11-6 libxcb1 libxau6 libxdmcp6 \
libbsd0 libmd0 mesa-utils xvfb \
&& rm -rf /var/lib/apt/lists/*
ENV LIBGL_ALWAYS_SOFTWARE=1
WORKDIR /app

View File

@ -54,6 +54,18 @@ MetaCore 已经超过单纯编辑器 Demo 阶段,当前具备:
### 1. 发布与部署还不具备正式交付厚度
#### Linux 首发执行状态2026-07-13
- [x] 建立 Linux x86_64 `BuildProfile`、公共发布服务以及 CLI / Editor 共用入口。
- [x] 建立 `Build -> Cook -> Stage -> Package -> Validate` 命令链和原子 staging。
- [x] 生成运行时项目描述、SHA-256 Release Manifest、便携目录、归档和独立符号包。
- [x] Player 支持严格交付模式、任意工作目录启动、版本/缺失/损坏错误码和有限帧 smoke 参数。
- [x] 增加日志轮转、Crashpad 本地 Minidump 接口、handler staging 和现场诊断包导出。
- [ ] 解决 Crashpad vcpkg 端口与当前 libc++ 15 的兼容问题,并完成真实 Minidump/符号回溯验收。
- [x] 增加 Ubuntu 22.04 clean-container 验证脚本与镜像定义。
- [ ] 在正式 Ubuntu 22.04 clean image 完成 GPU/UI smoke 实机验收。
- [ ] 实现并验收 Windows 对等后端;完成前本问题不能全平台关闭。
#### 问题
- Cook 已存在,但尚未形成统一的一键 Build / Package 工作流。

View File

@ -1,7 +1,7 @@
# MetaCore 打包与交付运行时工作流设计
生成时间2026-03-28
状态:草案
状态:Linux 首发实现中
范围M4 UI、持久化与打包循环
## 目的
@ -15,6 +15,31 @@
- 打包后客户端如何在独立环境运行
- 哪些文件属于开发期,哪些属于交付期
## 2026-07 Linux 首发实现
当前主干已经落地以下正式发布接口:
- `BuildProfiles/*.mcbuildprofile.json` 定义 Linux x86_64 的 Debug、Development、Release 构建。
- `MetaCoreBuildTool build` 执行 `Build -> Cook -> Stage -> Package -> Validate`,使用临时目录并在成功后提交。
- `MetaCoreBuildTool validate` 根据 SHA-256 Manifest 检查必需文件、大小、Hash、Runtime ABI 和路径安全。
- `MetaCoreDiagnosticsTool export` 导出 Manifest、校验结果、日志、Runtime 诊断和本地 Crashpad 数据。
- Player 默认从可执行文件旁的 `Project/MetaCore.runtimeproject` 进入严格交付模式;开发目录猜测只在显式开发模式保留。
- `scripts/validate_linux_release.sh``ci/linux-runtime-validation.Dockerfile` 提供 Ubuntu 22.04 隔离验证入口。
Crashpad 集成位于可选 vcpkg feature `crash-reporting`。当前固定端口在 libc++ 15 下因 `std::ranges::copy` 支持不足无法完成构建,因此默认构建保留接口但不宣称 Minidump 已验收;升级 libc++ 或加入经过维护的端口补丁后,发布流水线会强制收集 `crashpad_handler`
标准命令:
```bash
MetaCoreBuildTool build \
--project /path/to/project \
--profile /path/to/project/BuildProfiles/LinuxDevelopment.mcbuildprofile.json \
--engine-build /path/to/metacore/build/linux-development \
--clean
```
Linux 首发完成不代表 Windows 已验收Windows 仍需复用同一 Profile、Manifest、Player 严格模式和验证协议完成独立平台验收。
## 结论先说
MetaCore 第一阶段应明确采用下面这条交付链:

View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 2 ]]; then
echo "Usage: validate_linux_release.sh <MetaCoreBuildTool> <package-directory> [--smoke]" >&2
exit 2
fi
build_tool="$(realpath "$1")"
package="$(realpath "$2")"
"$build_tool" validate --package "$package"
docker run --rm --network none --read-only \
--tmpfs /tmp --tmpfs /app/Diagnostics \
-v "$package:/app:ro" \
metacore-runtime-validation:ubuntu22.04 \
/app/MetaCorePlayer --validate-only
if [[ "${3:-}" == "--smoke" ]]; then
docker run --rm --network none \
--tmpfs /tmp --tmpfs /app/Diagnostics \
-v "$package:/app:ro" \
metacore-runtime-validation:ubuntu22.04 \
xvfb-run -a /app/MetaCorePlayer --smoke-test-frames 3
fi

View File

@ -0,0 +1,87 @@
#include "MetaCoreDelivery/MetaCoreBuildPipeline.h"
#include "MetaCoreFoundation/MetaCoreAssetDependencyGraph.h"
#include "MetaCoreFoundation/MetaCoreHash.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
namespace {
int Failures = 0;
void Expect(bool condition, const char* message) {
if (!condition) {
std::cerr << "FAILED: " << message << '\n';
++Failures;
}
}
void WriteText(const std::filesystem::path& path, std::string_view value) {
std::filesystem::create_directories(path.parent_path());
std::ofstream output(path, std::ios::binary);
output << value;
}
} // namespace
int main() {
const std::string abc = "abc";
const auto* abcBytes = reinterpret_cast<const std::byte*>(abc.data());
Expect(
MetaCore::MetaCoreSha256Bytes(std::span(abcBytes, abc.size())) ==
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
"SHA-256 known vector"
);
const auto temporary = std::filesystem::temp_directory_path() / "MetaCoreDeliveryTests";
std::error_code error;
std::filesystem::remove_all(temporary, error);
WriteText(temporary / "unsafe.mcbuildprofile.json", R"({
"format_version":1,"name":"unsafe","target_platform":"Linux-x86_64","configuration":"Development",
"graphics_backend":"OpenGL","startup_scene":"../outside","output_directory":"Build","resource_policy":"ReferencedOnly"
})");
MetaCore::MetaCoreBuildIssue profileIssue;
Expect(!MetaCore::MetaCoreReadBuildProfile(temporary / "unsafe.mcbuildprofile.json", &profileIssue), "unsafe profile rejected");
Expect(profileIssue.Code == MetaCore::MetaCoreBuildErrorCode::UnsafePath, "unsafe profile error code");
const auto package = temporary / "Package";
WriteText(package / "payload.bin", "payload");
MetaCore::MetaCoreRuntimeDeliveryDocument delivery;
delivery.BuildId = "test-build";
delivery.StartupScene = "payload.bin";
Expect(MetaCore::MetaCoreWriteRuntimeDeliveryDocument(package / "Project" / "MetaCore.runtimeproject", delivery), "write runtime delivery");
MetaCore::MetaCoreReleaseManifestDocument manifest;
manifest.BuildId = delivery.BuildId;
const auto payloadHash = MetaCore::MetaCoreSha256File(package / "payload.bin");
const auto deliveryHash = MetaCore::MetaCoreSha256File(package / "Project" / "MetaCore.runtimeproject");
manifest.Files.push_back({"payload.bin", "asset", 7U, payloadHash.value_or(""), true});
manifest.Files.push_back({"Project/MetaCore.runtimeproject", "runtime_project",
std::filesystem::file_size(package / "Project" / "MetaCore.runtimeproject"), deliveryHash.value_or(""), true});
Expect(MetaCore::MetaCoreWriteReleaseManifest(package / "Manifest" / "MetaCore.release.json", manifest), "write release manifest");
Expect(MetaCore::MetaCoreValidateReleasePackage(package).empty(), "valid package accepted");
WriteText(package / "payload.bin", "tampered");
const auto corruptIssues = MetaCore::MetaCoreValidateReleasePackage(package);
Expect(!corruptIssues.empty(), "corrupt package rejected");
Expect(corruptIssues.front().Code == MetaCore::MetaCoreBuildErrorCode::FileSizeMismatch ||
corruptIssues.front().Code == MetaCore::MetaCoreBuildErrorCode::FileHashMismatch, "corrupt package error code");
std::filesystem::remove(package / "payload.bin", error);
const auto missingIssues = MetaCore::MetaCoreValidateReleasePackage(package);
Expect(!missingIssues.empty() && missingIssues.front().Code == MetaCore::MetaCoreBuildErrorCode::RequiredFileMissing, "missing file reported");
auto& graph = MetaCore::MetaCoreAssetDependencyGraph::Get();
graph.Clear();
const auto root = MetaCore::MetaCoreAssetGuid::Generate();
const auto middle = MetaCore::MetaCoreAssetGuid::Generate();
const auto leaf = MetaCore::MetaCoreAssetGuid::Generate();
graph.SetDependencies(root, {middle});
graph.SetDependencies(middle, {leaf});
const auto closure = graph.GetDependencyClosure({root});
Expect(closure.size() == 3U && closure.contains(root) && closure.contains(middle) && closure.contains(leaf), "transitive dependency closure");
graph.Clear();
std::filesystem::remove_all(temporary, error);
if (Failures == 0) std::cout << "MetaCoreDeliveryTests passed\n";
return Failures == 0 ? 0 : 1;
}

View File

@ -0,0 +1,73 @@
#include "MetaCoreDelivery/MetaCoreBuildPipeline.h"
#include <filesystem>
#include <iostream>
#include <string_view>
namespace {
void PrintIssue(const MetaCore::MetaCoreBuildIssue& issue) {
std::cerr << MetaCore::MetaCoreBuildErrorCodeName(issue.Code) << ": " << issue.Message;
if (!issue.Path.empty()) std::cerr << " path=" << issue.Path.generic_string();
if (!issue.Expected.empty()) std::cerr << " expected=" << issue.Expected;
if (!issue.Actual.empty()) std::cerr << " actual=" << issue.Actual;
std::cerr << '\n';
}
[[nodiscard]] const char* NextValue(int& index, int argc, char* argv[]) {
if (index + 1 >= argc) return nullptr;
return argv[++index];
}
void PrintUsage() {
std::cerr
<< "Usage:\n"
<< " MetaCoreBuildTool build --project <path> --profile <path> --engine-build <path> [--clean] [--skip-build]\n"
<< " MetaCoreBuildTool validate --package <path>\n";
}
} // namespace
int main(int argc, char* argv[]) {
if (argc < 2) { PrintUsage(); return 2; }
const std::string_view command(argv[1]);
if (command == "validate") {
std::filesystem::path package;
for (int index = 2; index < argc; ++index) {
if (std::string_view(argv[index]) == "--package") {
const char* value = NextValue(index, argc, argv);
if (value != nullptr) package = value;
}
}
if (package.empty()) { PrintUsage(); return 2; }
const auto issues = MetaCore::MetaCoreValidateReleasePackage(package);
for (const auto& issue : issues) PrintIssue(issue);
if (!issues.empty()) return 1;
std::cout << "MetaCore release package is valid: " << package.generic_string() << '\n';
return 0;
}
if (command != "build") { PrintUsage(); return 2; }
MetaCore::MetaCoreBuildRequest request;
for (int index = 2; index < argc; ++index) {
const std::string_view argument(argv[index]);
if (argument == "--project") {
const char* value = NextValue(index, argc, argv); if (value != nullptr) request.ProjectRoot = value;
} else if (argument == "--profile") {
const char* value = NextValue(index, argc, argv); if (value != nullptr) request.ProfilePath = value;
} else if (argument == "--engine-build") {
const char* value = NextValue(index, argc, argv); if (value != nullptr) request.EngineBuildRoot = value;
} else if (argument == "--clean") request.Clean = true;
else if (argument == "--skip-build") request.SkipBuild = true;
}
MetaCore::MetaCoreBuildPipeline pipeline;
const auto result = pipeline.Run(request, [](MetaCore::MetaCoreBuildStage stage, std::string_view message) {
std::cout << '[' << MetaCore::MetaCoreBuildStageName(stage) << "] " << message << '\n';
});
for (const auto& issue : result.Issues) PrintIssue(issue);
if (!result.Success) return 1;
std::cout << "package=" << result.PackageDirectory.generic_string() << '\n'
<< "archive=" << result.ArchivePath.generic_string() << '\n';
if (!result.SymbolsArchivePath.empty()) std::cout << "symbols=" << result.SymbolsArchivePath.generic_string() << '\n';
return 0;
}

View File

@ -0,0 +1,23 @@
#include "MetaCoreDelivery/MetaCoreBuildPipeline.h"
#include <filesystem>
#include <iostream>
#include <string_view>
int main(int argc, char* argv[]) {
if (argc != 6 || std::string_view(argv[1]) != "export" || std::string_view(argv[2]) != "--package" ||
std::string_view(argv[4]) != "--output") {
std::cerr << "Usage: MetaCoreDiagnosticsTool export --package <path> --output <archive.tar.gz>\n";
return 2;
}
const std::filesystem::path package = argv[3];
const std::filesystem::path output = argv[5];
MetaCore::MetaCoreBuildIssue issue;
if (!MetaCore::MetaCoreExportDiagnostics(package, output, &issue)) {
std::cerr << MetaCore::MetaCoreBuildErrorCodeName(issue.Code) << ": " << issue.Message
<< " path=" << issue.Path.generic_string() << '\n';
return 1;
}
std::cout << "diagnostics=" << output.generic_string() << '\n';
return 0;
}

View File

@ -25,5 +25,11 @@
],
"platform": "!windows"
}
]
],
"features": {
"crash-reporting": {
"description": "Enable local Crashpad Minidump capture and package crashpad_handler.",
"dependencies": ["crashpad"]
}
}
}