refactor(srp): unify engine managed assembly discovery
This commit is contained in:
@@ -37,8 +37,11 @@
|
||||
#include <mono/metadata/reflection.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -357,6 +360,197 @@ std::string TrimAssemblyName(const std::string& assemblyName) {
|
||||
return assemblyName;
|
||||
}
|
||||
|
||||
std::string TrimWhitespace(std::string value) {
|
||||
auto isWhitespace = [](unsigned char ch) {
|
||||
return std::isspace(ch) != 0;
|
||||
};
|
||||
|
||||
value.erase(
|
||||
value.begin(),
|
||||
std::find_if(
|
||||
value.begin(),
|
||||
value.end(),
|
||||
[&](char ch) {
|
||||
return !isWhitespace(static_cast<unsigned char>(ch));
|
||||
}));
|
||||
value.erase(
|
||||
std::find_if(
|
||||
value.rbegin(),
|
||||
value.rend(),
|
||||
[&](char ch) {
|
||||
return !isWhitespace(static_cast<unsigned char>(ch));
|
||||
}).base(),
|
||||
value.end());
|
||||
return value;
|
||||
}
|
||||
|
||||
bool NormalizeManagedAssemblyDescriptor(
|
||||
MonoScriptRuntime::ManagedAssemblyDescriptor& descriptor,
|
||||
const std::filesystem::path& assemblyDirectory,
|
||||
std::unordered_set<std::string>& ioAssemblyNames,
|
||||
std::string* outError) {
|
||||
descriptor.name = TrimAssemblyName(TrimWhitespace(descriptor.name));
|
||||
if (descriptor.name.empty() && !descriptor.path.empty()) {
|
||||
descriptor.name =
|
||||
TrimAssemblyName(descriptor.path.stem().string());
|
||||
}
|
||||
|
||||
if (descriptor.name.empty()) {
|
||||
if (outError != nullptr) {
|
||||
*outError = "Managed engine assembly name is empty.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (descriptor.path.empty() && !assemblyDirectory.empty()) {
|
||||
descriptor.path = assemblyDirectory / (descriptor.name + ".dll");
|
||||
}
|
||||
|
||||
if (!descriptor.path.empty()) {
|
||||
descriptor.path = descriptor.path.lexically_normal();
|
||||
}
|
||||
|
||||
if (!ioAssemblyNames.insert(descriptor.name).second) {
|
||||
if (outError != nullptr) {
|
||||
*outError =
|
||||
"Managed engine assembly name is duplicated: " +
|
||||
descriptor.name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadManagedAssemblyManifest(
|
||||
const std::filesystem::path& assemblyDirectory,
|
||||
const std::filesystem::path& manifestPath,
|
||||
std::vector<MonoScriptRuntime::ManagedAssemblyDescriptor>& outAssemblies,
|
||||
std::string* outError) {
|
||||
outAssemblies.clear();
|
||||
|
||||
std::error_code ec;
|
||||
if (manifestPath.empty() ||
|
||||
!std::filesystem::exists(manifestPath, ec)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream input(manifestPath);
|
||||
if (!input.is_open()) {
|
||||
if (outError != nullptr) {
|
||||
*outError =
|
||||
"Failed to open managed engine assembly manifest: " +
|
||||
manifestPath.string();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> assemblyNames;
|
||||
std::string line;
|
||||
size_t lineNumber = 0u;
|
||||
while (std::getline(input, line)) {
|
||||
++lineNumber;
|
||||
|
||||
const size_t commentStart = line.find('#');
|
||||
if (commentStart != std::string::npos) {
|
||||
line.erase(commentStart);
|
||||
}
|
||||
|
||||
line = TrimWhitespace(std::move(line));
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MonoScriptRuntime::ManagedAssemblyDescriptor descriptor;
|
||||
const size_t separator = line.find('=');
|
||||
if (separator == std::string::npos) {
|
||||
descriptor.name = line;
|
||||
} else {
|
||||
descriptor.name = line.substr(0u, separator);
|
||||
std::string relativePath =
|
||||
TrimWhitespace(line.substr(separator + 1u));
|
||||
if (!relativePath.empty()) {
|
||||
descriptor.path =
|
||||
(assemblyDirectory / relativePath).lexically_normal();
|
||||
}
|
||||
}
|
||||
|
||||
if (!NormalizeManagedAssemblyDescriptor(
|
||||
descriptor,
|
||||
assemblyDirectory,
|
||||
assemblyNames,
|
||||
outError)) {
|
||||
if (outError != nullptr && !outError->empty()) {
|
||||
*outError +=
|
||||
" Manifest: " + manifestPath.string() +
|
||||
" line " + std::to_string(lineNumber) + ".";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
outAssemblies.push_back(std::move(descriptor));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DiscoverManagedAssembliesByConvention(
|
||||
const MonoScriptRuntime::Settings& settings,
|
||||
std::vector<MonoScriptRuntime::ManagedAssemblyDescriptor>& outAssemblies) {
|
||||
outAssemblies.clear();
|
||||
|
||||
std::error_code ec;
|
||||
if (settings.assemblyDirectory.empty() ||
|
||||
!std::filesystem::exists(settings.assemblyDirectory, ec)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string coreAssemblyName =
|
||||
TrimAssemblyName(settings.coreAssemblyName);
|
||||
const std::string appAssemblyName =
|
||||
TrimAssemblyName(settings.appAssemblyName);
|
||||
std::unordered_set<std::string> reservedNames = {
|
||||
coreAssemblyName,
|
||||
appAssemblyName,
|
||||
"mscorlib"};
|
||||
|
||||
for (std::filesystem::directory_iterator it(
|
||||
settings.assemblyDirectory,
|
||||
ec), end;
|
||||
it != end && !ec;
|
||||
it.increment(ec)) {
|
||||
if (ec || !it->is_regular_file(ec)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::filesystem::path path = it->path();
|
||||
if (path.extension() != ".dll") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string assemblyName =
|
||||
TrimAssemblyName(path.stem().string());
|
||||
if (assemblyName.rfind("XCEngine.", 0u) != 0u ||
|
||||
reservedNames.contains(assemblyName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
outAssemblies.push_back(
|
||||
MonoScriptRuntime::ManagedAssemblyDescriptor{
|
||||
assemblyName,
|
||||
path.lexically_normal()});
|
||||
}
|
||||
|
||||
std::sort(
|
||||
outAssemblies.begin(),
|
||||
outAssemblies.end(),
|
||||
[](const MonoScriptRuntime::ManagedAssemblyDescriptor& lhs,
|
||||
const MonoScriptRuntime::ManagedAssemblyDescriptor& rhs) {
|
||||
return lhs.name < rhs.name;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
MonoScriptRuntime::ManagedAssemblyDescriptor BuildManagedAssemblyDescriptor(
|
||||
const std::string& assemblyName,
|
||||
const std::filesystem::path& assemblyPath) {
|
||||
@@ -3799,8 +3993,10 @@ MonoScriptRuntime::~MonoScriptRuntime() {
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::Initialize() {
|
||||
ResolveSettings();
|
||||
m_lastError.clear();
|
||||
if (!ResolveSettings()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_initialized) {
|
||||
return true;
|
||||
@@ -4293,7 +4489,61 @@ size_t MonoScriptRuntime::InstanceKeyHasher::operator()(const InstanceKey& key)
|
||||
return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2));
|
||||
}
|
||||
|
||||
void MonoScriptRuntime::ResolveSettings() {
|
||||
std::filesystem::path MonoScriptRuntime::GetEngineAssemblyManifestPath(
|
||||
const std::filesystem::path& assemblyDirectory) {
|
||||
return assemblyDirectory / EngineAssemblyManifestFileName;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::DiscoverEngineAssemblies(
|
||||
Settings& ioSettings,
|
||||
std::string* outError) {
|
||||
if (!ioSettings.engineAssemblies.empty()) {
|
||||
std::unordered_set<std::string> assemblyNames;
|
||||
for (ManagedAssemblyDescriptor& assembly :
|
||||
ioSettings.engineAssemblies) {
|
||||
if (!NormalizeManagedAssemblyDescriptor(
|
||||
assembly,
|
||||
ioSettings.assemblyDirectory,
|
||||
assemblyNames,
|
||||
outError)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ioSettings.assemblyDirectory.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<ManagedAssemblyDescriptor> discoveredAssemblies;
|
||||
const std::filesystem::path manifestPath =
|
||||
GetEngineAssemblyManifestPath(ioSettings.assemblyDirectory);
|
||||
if (!LoadManagedAssemblyManifest(
|
||||
ioSettings.assemblyDirectory,
|
||||
manifestPath,
|
||||
discoveredAssemblies,
|
||||
outError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (discoveredAssemblies.empty() &&
|
||||
!DiscoverManagedAssembliesByConvention(
|
||||
ioSettings,
|
||||
discoveredAssemblies)) {
|
||||
if (outError != nullptr) {
|
||||
*outError =
|
||||
"Failed to discover managed engine assemblies in " +
|
||||
ioSettings.assemblyDirectory.string();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ioSettings.engineAssemblies = std::move(discoveredAssemblies);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::ResolveSettings() {
|
||||
if (!m_settings.coreAssemblyPath.empty() && m_settings.assemblyDirectory.empty()) {
|
||||
m_settings.assemblyDirectory = m_settings.coreAssemblyPath.parent_path();
|
||||
}
|
||||
@@ -4318,18 +4568,13 @@ void MonoScriptRuntime::ResolveSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
for (ManagedAssemblyDescriptor& assembly : m_settings.engineAssemblies) {
|
||||
if (assembly.name.empty() && !assembly.path.empty()) {
|
||||
assembly.name = assembly.path.stem().string();
|
||||
}
|
||||
|
||||
if (assembly.path.empty() &&
|
||||
!m_settings.assemblyDirectory.empty() &&
|
||||
!assembly.name.empty()) {
|
||||
assembly.path =
|
||||
m_settings.assemblyDirectory / (assembly.name + ".dll");
|
||||
}
|
||||
std::string discoveryError;
|
||||
if (!DiscoverEngineAssemblies(m_settings, &discoveryError)) {
|
||||
SetError(discoveryError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MonoScriptRuntime::InitializeRootDomain() {
|
||||
|
||||
Reference in New Issue
Block a user