2
0
Эх сурвалжийг харах

Merge pull request #30428 from neikeq/gitcheckout-b

Misc Mono Fixes
Rémi Verschelde 6 жил өмнө
parent
commit
29ca79bd68

+ 51 - 35
modules/mono/csharp_script.cpp

@@ -867,17 +867,26 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 
 			script->reload(p_soft_reload);
 			script->update_exports();
+
+			if (!script->valid) {
+				script->pending_reload_instances.clear();
+				continue;
+			}
 		} else {
 			const StringName &class_namespace = script->tied_class_namespace_for_reload;
 			const StringName &class_name = script->tied_class_name_for_reload;
 			GDMonoAssembly *project_assembly = gdmono->get_project_assembly();
-			GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly();
 
 			// Search in project and tools assemblies first as those are the most likely to have the class
 			GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : NULL);
+
+#ifdef TOOLS_ENABLED
 			if (!script_class) {
+				GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly();
 				script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL);
 			}
+#endif
+
 			if (!script_class) {
 				script_class = gdmono->get_class(class_namespace, class_name);
 			}
@@ -897,12 +906,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 
 			GDMonoClass *native = GDMonoUtils::get_class_native_base(script_class);
 
-			Ref<CSharpScript> new_script = CSharpScript::create_for_managed_type(script_class, native);
-			CRASH_COND(new_script.is_null());
-
-			new_script->pending_reload_instances = script->pending_reload_instances;
-			new_script->pending_reload_state = script->pending_reload_state;
-			script = new_script;
+			CSharpScript::initialize_for_managed_type(script, script_class, native);
 		}
 
 		String native_name = NATIVE_GDMONOCLASS_NAME(script->native);
@@ -953,7 +957,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 				CRASH_COND(si != NULL);
 #endif
 				// Re-create script instance
-
 				obj->set_script(script.get_ref_ptr()); // will create the script instance as well
 			}
 		}
@@ -1203,7 +1206,9 @@ CSharpLanguage::CSharpLanguage() {
 
 	scripts_metadata_invalidated = true;
 
+#ifdef TOOLS_ENABLED
 	godotsharp_editor = NULL;
+#endif
 }
 
 CSharpLanguage::~CSharpLanguage() {
@@ -2144,7 +2149,6 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List
 		propnames.push_back(E->get());
 	}
 }
-#endif
 
 void CSharpScript::_update_member_info_no_exports() {
 
@@ -2191,6 +2195,7 @@ void CSharpScript::_update_member_info_no_exports() {
 		}
 	}
 }
+#endif
 
 bool CSharpScript::_update_exports() {
 
@@ -2673,35 +2678,46 @@ void CSharpScript::_bind_methods() {
 
 Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) {
 
-	// This method should not fail
+	// This method should not fail, only assertions allowed
 
 	CRASH_COND(p_class == NULL);
 
 	// TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time
 	Ref<CSharpScript> script = memnew(CSharpScript);
 
-	script->name = p_class->get_name();
-	script->script_class = p_class;
-	script->native = p_native;
+	initialize_for_managed_type(script, p_class, p_native);
 
-	CRASH_COND(script->native == NULL);
+	return script;
+}
+
+void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) {
+
+	// This method should not fail, only assertions allowed
+
+	CRASH_COND(p_class == NULL);
+
+	p_script->name = p_class->get_name();
+	p_script->script_class = p_class;
+	p_script->native = p_native;
 
-	GDMonoClass *base = script->script_class->get_parent_class();
+	CRASH_COND(p_script->native == NULL);
 
-	if (base != script->native)
-		script->base = base;
+	GDMonoClass *base = p_script->script_class->get_parent_class();
 
-	script->valid = true;
-	script->tool = script->script_class->has_attribute(CACHED_CLASS(ToolAttribute));
+	if (base != p_script->native)
+		p_script->base = base;
 
-	if (!script->tool) {
-		GDMonoClass *nesting_class = script->script_class->get_nesting_class();
-		script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
+	p_script->valid = true;
+	p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute));
+
+	if (!p_script->tool) {
+		GDMonoClass *nesting_class = p_script->script_class->get_nesting_class();
+		p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
 	}
 
 #if TOOLS_ENABLED
-	if (!script->tool) {
-		script->tool = script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
+	if (!p_script->tool) {
+		p_script->tool = p_script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
 	}
 #endif
 
@@ -2710,10 +2726,10 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
 	// Native base methods must be fetched before the current class.
 	// Not needed if the script class itself is a native class.
 
-	if (script->script_class != script->native) {
-		GDMonoClass *native_top = script->native;
+	if (p_script->script_class != p_script->native) {
+		GDMonoClass *native_top = p_script->native;
 		while (native_top) {
-			native_top->fetch_methods_with_godot_api_checks(script->native);
+			native_top->fetch_methods_with_godot_api_checks(p_script->native);
 
 			if (native_top == CACHED_CLASS(GodotObject))
 				break;
@@ -2723,19 +2739,19 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
 	}
 #endif
 
-	script->script_class->fetch_methods_with_godot_api_checks(script->native);
+	p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native);
 
 	// Need to fetch method from base classes as well
-	GDMonoClass *top = script->script_class;
-	while (top && top != script->native) {
-		top->fetch_methods_with_godot_api_checks(script->native);
+	GDMonoClass *top = p_script->script_class;
+	while (top && top != p_script->native) {
+		top->fetch_methods_with_godot_api_checks(p_script->native);
 		top = top->get_parent_class();
 	}
 
-	script->load_script_signals(script->script_class, script->native);
-	script->_update_member_info_no_exports();
-
-	return script;
+	p_script->load_script_signals(p_script->script_class, p_script->native);
+#ifdef TOOLS_ENABLED
+	p_script->_update_member_info_no_exports();
+#endif
 }
 
 bool CSharpScript::can_instance() const {

+ 4 - 1
modules/mono/csharp_script.h

@@ -121,6 +121,7 @@ class CSharpScript : public Script {
 	bool placeholder_fallback_enabled;
 	bool exports_invalidated;
 	void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames);
+	void _update_member_info_no_exports();
 	virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder);
 #endif
 
@@ -131,7 +132,6 @@ class CSharpScript : public Script {
 	void load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class);
 	bool _get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> &params);
 
-	void _update_member_info_no_exports();
 	bool _update_exports();
 #ifdef TOOLS_ENABLED
 	bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported);
@@ -144,6 +144,7 @@ class CSharpScript : public Script {
 	// Do not use unless you know what you are doing
 	friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
 	static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native);
+	static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native);
 
 protected:
 	static void _bind_methods();
@@ -354,7 +355,9 @@ public:
 
 	_FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; }
 
+#ifdef TOOLS_ENABLED
 	_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
+#endif
 
 	static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle);
 	static void release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle);

+ 1 - 1
modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs

@@ -58,7 +58,7 @@ namespace GodotTools
                 {
                     var oldFileDict = (Dictionary) oldFileVar;
 
-                    if (ulong.TryParse((string) oldFileDict["modified_time"], out ulong storedModifiedTime))
+                    if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime))
                     {
                         if (storedModifiedTime == modifiedTime)
                         {

+ 13 - 13
modules/mono/editor/bindings_generator.cpp

@@ -875,14 +875,14 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vect
 	da->make_dir("Core");
 	da->make_dir("ObjectType");
 
-	String core_dir = path_join(p_proj_dir, "Core");
-	String obj_type_dir = path_join(p_proj_dir, "ObjectType");
+	String core_dir = path::join(p_proj_dir, "Core");
+	String obj_type_dir = path::join(p_proj_dir, "ObjectType");
 
 	// Generate source file for global scope constants and enums
 	{
 		StringBuilder constants_source;
 		_generate_global_constants(constants_source);
-		String output_file = path_join(core_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs");
+		String output_file = path::join(core_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs");
 		Error save_err = _save_file(output_file, constants_source);
 		if (save_err != OK)
 			return save_err;
@@ -896,7 +896,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vect
 		if (itype.api_type == ClassDB::API_EDITOR)
 			continue;
 
-		String output_file = path_join(obj_type_dir, itype.proxy_name + ".cs");
+		String output_file = path::join(obj_type_dir, itype.proxy_name + ".cs");
 		Error err = _generate_cs_type(itype, output_file);
 
 		if (err == ERR_SKIP)
@@ -917,7 +917,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vect
 		const String &file_name = E->key();
 		const GodotCsCompressedFile &file_data = E->value();
 
-		String output_file = path_join(core_dir, file_name);
+		String output_file = path::join(core_dir, file_name);
 
 		Vector<uint8_t> data;
 		data.resize(file_data.uncompressed_size);
@@ -971,7 +971,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vect
 
 	cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK);
 
-	String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs");
+	String internal_methods_file = path::join(core_dir, BINDINGS_CLASS_NATIVECALLS ".cs");
 
 	Error err = _save_file(internal_methods_file, cs_icalls_content);
 	if (err != OK)
@@ -996,8 +996,8 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Ve
 	da->make_dir("Core");
 	da->make_dir("ObjectType");
 
-	String core_dir = path_join(p_proj_dir, "Core");
-	String obj_type_dir = path_join(p_proj_dir, "ObjectType");
+	String core_dir = path::join(p_proj_dir, "Core");
+	String obj_type_dir = path::join(p_proj_dir, "ObjectType");
 
 	for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) {
 		const TypeInterface &itype = E.get();
@@ -1005,7 +1005,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Ve
 		if (itype.api_type != ClassDB::API_EDITOR)
 			continue;
 
-		String output_file = path_join(obj_type_dir, itype.proxy_name + ".cs");
+		String output_file = path::join(obj_type_dir, itype.proxy_name + ".cs");
 		Error err = _generate_cs_type(itype, output_file);
 
 		if (err == ERR_SKIP)
@@ -1051,7 +1051,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Ve
 
 	cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK);
 
-	String internal_methods_file = path_join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs");
+	String internal_methods_file = path::join(core_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs");
 
 	Error err = _save_file(internal_methods_file, cs_icalls_content);
 	if (err != OK)
@@ -1064,7 +1064,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Ve
 
 Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {
 
-	String output_dir = DirAccess::get_full_path(p_output_dir, DirAccess::ACCESS_FILESYSTEM);
+	String output_dir = path::abspath(path::realpath(p_output_dir));
 
 	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
 	ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
@@ -1862,7 +1862,7 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
 
 	output.append("\n#endif // MONO_GLUE_ENABLED\n");
 
-	Error save_err = _save_file(path_join(p_output_dir, "mono_glue.gen.cpp"), output);
+	Error save_err = _save_file(path::join(p_output_dir, "mono_glue.gen.cpp"), output);
 	if (save_err != OK)
 		return save_err;
 
@@ -2192,7 +2192,7 @@ void BindingsGenerator::_populate_object_type_interfaces() {
 
 		itype.base_name = ClassDB::get_parent_class(type_cname);
 		itype.is_singleton = Engine::get_singleton()->has_singleton(itype.proxy_name);
-		itype.is_instantiable = ClassDB::can_instance(type_cname) && !itype.is_singleton;
+		itype.is_instantiable = class_info->creation_func && !itype.is_singleton;
 		itype.is_reference = ClassDB::is_parent_class(type_cname, name_cache.type_Reference);
 		itype.memory_own = itype.is_reference;
 

+ 12 - 1
modules/mono/glue/base_object_glue.cpp

@@ -219,7 +219,18 @@ MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString *
 }
 
 MonoString *godot_icall_Object_ToString(Object *p_ptr) {
-	return GDMonoMarshal::mono_string_from_godot(Variant(p_ptr).operator String());
+#ifdef DEBUG_ENABLED
+	// Cannot happen in C#; would get an ObjectDisposedException instead.
+	CRASH_COND(p_ptr == NULL);
+
+	if (ScriptDebugger::get_singleton() && !Object::cast_to<Reference>(p_ptr)) { // Only if debugging!
+		// Cannot happen either in C#; the handle is nullified when the object is destroyed
+		CRASH_COND(!ObjectDB::instance_validate(p_ptr));
+	}
+#endif
+
+	String result = "[" + p_ptr->get_class() + ":" + itos(p_ptr->get_instance_id()) + "]";
+	return GDMonoMarshal::mono_string_from_godot(result);
 }
 
 void godot_register_object_icalls() {

+ 5 - 3
modules/mono/mono_gd/gd_mono.cpp

@@ -241,9 +241,9 @@ void GDMono::initialize() {
 		locations.push_back("/usr/local/var/homebrew/linked/mono/");
 
 		for (int i = 0; i < locations.size(); i++) {
-			String hint_assembly_rootdir = path_join(locations[i], "lib");
-			String hint_mscorlib_path = path_join(hint_assembly_rootdir, "mono", "4.5", "mscorlib.dll");
-			String hint_config_dir = path_join(locations[i], "etc");
+			String hint_assembly_rootdir = path::join(locations[i], "lib");
+			String hint_mscorlib_path = path::join(hint_assembly_rootdir, "mono", "4.5", "mscorlib.dll");
+			String hint_config_dir = path::join(locations[i], "etc");
 
 			if (FileAccess::exists(hint_mscorlib_path) && DirAccess::exists(hint_config_dir)) {
 				assembly_rootdir = hint_assembly_rootdir;
@@ -564,6 +564,7 @@ bool GDMono::_load_corlib_assembly() {
 	return success;
 }
 
+#ifdef TOOLS_ENABLED
 static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) {
 
 	// Create destination directory if needed
@@ -607,6 +608,7 @@ static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir,
 
 	return true;
 }
+#endif
 
 bool GDMono::_load_core_api_assembly() {
 

+ 93 - 34
modules/mono/utils/path_utils.cpp

@@ -36,16 +36,21 @@
 #include "core/project_settings.h"
 
 #ifdef WINDOWS_ENABLED
+#include <windows.h>
+
 #define ENV_PATH_SEP ";"
 #else
-#define ENV_PATH_SEP ":"
 #include <limits.h>
+#include <unistd.h>
+
+#define ENV_PATH_SEP ":"
 #endif
 
 #include <stdlib.h>
 
-String path_which(const String &p_name) {
+namespace path {
 
+String find_executable(const String &p_name) {
 #ifdef WINDOWS_ENABLED
 	Vector<String> exts = OS::get_singleton()->get_environment("PATHEXT").split(ENV_PATH_SEP, false);
 #endif
@@ -55,7 +60,7 @@ String path_which(const String &p_name) {
 		return String();
 
 	for (int i = 0; i < env_path.size(); i++) {
-		String p = path_join(env_path[i], p_name);
+		String p = path::join(env_path[i], p_name);
 
 #ifdef WINDOWS_ENABLED
 		for (int j = 0; j < exts.size(); j++) {
@@ -73,42 +78,96 @@ String path_which(const String &p_name) {
 	return String();
 }
 
-void fix_path(const String &p_path, String &r_out) {
-	r_out = p_path.replace("\\", "/");
+String cwd() {
+#ifdef WINDOWS_ENABLED
+	const DWORD expected_size = ::GetCurrentDirectoryW(0, NULL);
+
+	String buffer;
+	buffer.resize((int)expected_size);
+	if (::GetCurrentDirectoryW(expected_size, buffer.ptrw()) == 0)
+		return ".";
+
+	return buffer.simplify_path();
+#else
+	char buffer[PATH_MAX];
+	if (::getcwd(buffer, sizeof(buffer)) == NULL)
+		return ".";
+
+	String result;
+	if (result.parse_utf8(buffer))
+		return ".";
 
-	while (true) { // in case of using 2 or more slash
-		String compare = r_out.replace("//", "/");
-		if (r_out == compare)
-			break;
-		else
-			r_out = compare;
+	return result.simplify_path();
+#endif
+}
+
+String abspath(const String &p_path) {
+	if (p_path.is_abs_path()) {
+		return p_path.simplify_path();
+	} else {
+		return path::join(path::cwd(), p_path).simplify_path();
 	}
 }
 
-bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path) {
+String realpath(const String &p_path) {
 #ifdef WINDOWS_ENABLED
-	CharType ret[_MAX_PATH];
-	if (::_wfullpath(ret, p_existing_path.c_str(), _MAX_PATH)) {
-		String abspath = String(ret).replace("\\", "/");
-		int pos = abspath.find(":/");
-		if (pos != -1) {
-			r_abs_path = abspath.substr(pos - 1, abspath.length());
-		} else {
-			r_abs_path = abspath;
-		}
-		return true;
-	}
-#else
-	char *resolved_path = ::realpath(p_existing_path.utf8().get_data(), NULL);
-	if (resolved_path) {
-		String retstr;
-		bool success = !retstr.parse_utf8(resolved_path);
-		::free(resolved_path);
-		if (success) {
-			r_abs_path = retstr;
-			return true;
-		}
+	// Open file without read/write access
+	HANDLE hFile = ::CreateFileW(p_path.c_str(), 0,
+			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+			NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+	if (hFile == INVALID_HANDLE_VALUE)
+		return p_path;
+
+	const DWORD expected_size = ::GetFinalPathNameByHandleW(hFile, NULL, 0, FILE_NAME_NORMALIZED);
+
+	if (expected_size == 0) {
+		::CloseHandle(hFile);
+		return p_path;
 	}
+
+	String buffer;
+	buffer.resize((int)expected_size);
+	::GetFinalPathNameByHandleW(hFile, buffer.ptrw(), expected_size, FILE_NAME_NORMALIZED);
+
+	::CloseHandle(hFile);
+	return buffer.simplify_path();
+#elif UNIX_ENABLED
+	char *resolved_path = ::realpath(p_path.utf8().get_data(), NULL);
+
+	if (!resolved_path)
+		return p_path;
+
+	String result;
+	bool parse_ok = result.parse_utf8(resolved_path);
+	::free(resolved_path);
+
+	if (parse_ok)
+		return p_path;
+
+	return result.simplify_path();
 #endif
-	return false;
 }
+
+String join(const String &p_a, const String &p_b) {
+	if (p_a.empty())
+		return p_b;
+
+	const CharType a_last = p_a[p_a.length() - 1];
+	if ((a_last == '/' || a_last == '\\') ||
+			(p_b.size() > 0 && (p_b[0] == '/' || p_b[0] == '\\'))) {
+		return p_a + p_b;
+	}
+
+	return p_a + "/" + p_b;
+}
+
+String join(const String &p_a, const String &p_b, const String &p_c) {
+	return path::join(path::join(p_a, p_b), p_c);
+}
+
+String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d) {
+	return path::join(path::join(path::join(p_a, p_b), p_c), p_d);
+}
+
+} // namespace path

+ 20 - 12
modules/mono/utils/path_utils.h

@@ -31,24 +31,32 @@
 #ifndef PATH_UTILS_H
 #define PATH_UTILS_H
 
+#include "core/string_builder.h"
 #include "core/ustring.h"
 
-_FORCE_INLINE_ String path_join(const String &e1, const String &e2) {
-	return e1.plus_file(e2);
-}
+namespace path {
 
-_FORCE_INLINE_ String path_join(const String &e1, const String &e2, const String &e3) {
-	return e1.plus_file(e2).plus_file(e3);
-}
+String join(const String &p_a, const String &p_b);
+String join(const String &p_a, const String &p_b, const String &p_c);
+String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d);
 
-_FORCE_INLINE_ String path_join(const String &e1, const String &e2, const String &e3, const String &e4) {
-	return e1.plus_file(e2).plus_file(e3).plus_file(e4);
-}
+String find_executable(const String &p_name);
 
-String path_which(const String &p_name);
+/// Returns a normalized absolute path to the current working directory
+String cwd();
 
-void fix_path(const String &p_path, String &r_out);
+/**
+ * Obtains a normalized absolute path to p_path. Symbolic links are
+ * not resolved. The path p_path might not exist in the file system.
+ */
+String abspath(const String &p_path);
 
-bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path);
+/**
+ * Obtains a normalized path to p_path with symbolic links resolved.
+ * The resulting path might be either a relative or an absolute path.
+ */
+String realpath(const String &p_path);
+
+} // namespace path
 
 #endif // PATH_UTILS_H