结构优化
This commit is contained in:
parent
278313f739
commit
b10e03eda0
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
690
Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp
Normal file
690
Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp
Normal 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
|
||||
45
Source/MetaCoreDelivery/Private/MetaCoreCrashReporter.cpp
Normal file
45
Source/MetaCoreDelivery/Private/MetaCoreCrashReporter.cpp
Normal 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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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());
|
||||
|
||||
@ -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>());
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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"
|
||||
}
|
||||
9
ci/linux-runtime-validation.Dockerfile
Normal file
9
ci/linux-runtime-validation.Dockerfile
Normal 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
|
||||
@ -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 工作流。
|
||||
|
||||
@ -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 第一阶段应明确采用下面这条交付链:
|
||||
|
||||
25
scripts/validate_linux_release.sh
Executable file
25
scripts/validate_linux_release.sh
Executable 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
|
||||
87
tests/MetaCoreDeliveryTests.cpp
Normal file
87
tests/MetaCoreDeliveryTests.cpp
Normal 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;
|
||||
}
|
||||
73
tools/MetaCoreBuildTool/main.cpp
Normal file
73
tools/MetaCoreBuildTool/main.cpp
Normal 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;
|
||||
}
|
||||
23
tools/MetaCoreDiagnosticsTool/main.cpp
Normal file
23
tools/MetaCoreDiagnosticsTool/main.cpp
Normal 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;
|
||||
}
|
||||
@ -25,5 +25,11 @@
|
||||
],
|
||||
"platform": "!windows"
|
||||
}
|
||||
]
|
||||
],
|
||||
"features": {
|
||||
"crash-reporting": {
|
||||
"description": "Enable local Crashpad Minidump capture and package crashpad_handler.",
|
||||
"dependencies": ["crashpad"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user