#pragma once #include "Platform/Win32Utf8.h" #include #include #include #include #include namespace XCEngine { namespace Editor { namespace fs = std::filesystem; namespace detail { inline fs::path NormalizePath(const fs::path& path, const fs::path& basePath = {}) { if (path.empty()) { return {}; } fs::path absolutePath = path; std::error_code ec; if (absolutePath.is_relative()) { absolutePath = basePath.empty() ? fs::absolute(absolutePath, ec) : fs::absolute(basePath / absolutePath, ec); if (ec) { absolutePath = basePath.empty() ? absolutePath : (basePath / path); } } ec.clear(); const fs::path canonicalPath = fs::weakly_canonical(absolutePath, ec); if (!ec) { return canonicalPath.lexically_normal(); } return absolutePath.lexically_normal(); } inline bool IsWorkspaceRoot(const fs::path& candidate) { std::error_code ec; return fs::exists(candidate / L"CMakeLists.txt", ec) && fs::is_directory(candidate / L"editor", ec) && fs::is_directory(candidate / L"engine", ec); } inline bool IsEditorProjectRoot(const fs::path& candidate) { std::error_code ec; return fs::exists(candidate / L"Project.xcproject", ec) || fs::is_directory(candidate / L"Assets", ec); } inline fs::path ResolveWorkspaceDefaultProjectRoot(const fs::path& workspaceRoot) { if (workspaceRoot.empty()) { return {}; } const fs::path preferredProjectRoot = workspaceRoot / L"project"; if (IsEditorProjectRoot(preferredProjectRoot)) { return NormalizePath(preferredProjectRoot); } if (IsEditorProjectRoot(workspaceRoot)) { return NormalizePath(workspaceRoot); } return NormalizePath(preferredProjectRoot); } inline std::optional FindWorkspaceRoot(fs::path start) { if (start.empty()) { return std::nullopt; } start = NormalizePath(start); for (fs::path current = start; !current.empty(); ) { if (IsWorkspaceRoot(current)) { return current; } const fs::path parent = current.parent_path(); if (parent == current) { break; } current = parent; } return std::nullopt; } inline std::optional ParseCommandLineProjectOverride(const fs::path& workingDirectory) { int argc = 0; LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); if (!argv || argc <= 1) { if (argv) { LocalFree(argv); } return std::nullopt; } std::optional projectOverride; for (int i = 1; i < argc; ++i) { const std::wstring arg = argv[i] ? argv[i] : L""; if ((arg == L"--project" || arg == L"-p") && i + 1 < argc) { projectOverride = NormalizePath(fs::path(argv[++i]), workingDirectory); break; } static const std::wstring kProjectArgPrefix = L"--project="; if (arg.rfind(kProjectArgPrefix, 0) == 0) { projectOverride = NormalizePath(fs::path(arg.substr(kProjectArgPrefix.size())), workingDirectory); break; } } LocalFree(argv); return projectOverride; } inline std::string PathToUtf8(const fs::path& path) { return Platform::WideToUtf8(path.wstring()); } } // namespace detail inline std::string ResolveEditorProjectRootUtf8() { std::error_code ec; const fs::path workingDirectory = detail::NormalizePath(fs::current_path(ec)); const fs::path executableDirectory = detail::NormalizePath(fs::path(Platform::Utf8ToWide(Platform::GetExecutableDirectoryUtf8()))); if (const auto projectOverride = detail::ParseCommandLineProjectOverride(workingDirectory.empty() ? executableDirectory : workingDirectory)) { return detail::PathToUtf8(*projectOverride); } if (const auto workspaceRoot = detail::FindWorkspaceRoot(workingDirectory)) { return detail::PathToUtf8(detail::ResolveWorkspaceDefaultProjectRoot(*workspaceRoot)); } if (const auto workspaceRoot = detail::FindWorkspaceRoot(executableDirectory)) { return detail::PathToUtf8(detail::ResolveWorkspaceDefaultProjectRoot(*workspaceRoot)); } if (!workingDirectory.empty()) { return detail::PathToUtf8(workingDirectory); } return detail::PathToUtf8(executableDirectory); } inline bool SetEditorWorkingDirectory(const std::string& projectRootUtf8) { if (projectRootUtf8.empty()) { return false; } std::error_code ec; fs::current_path(fs::path(Platform::Utf8ToWide(projectRootUtf8)), ec); return !ec; } } // namespace Editor } // namespace XCEngine