diff options
Diffstat (limited to 'modules/mono')
19 files changed, 690 insertions, 139 deletions
diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py index f66ffdb573..c47cfc8a38 100644 --- a/modules/mono/build_scripts/godot_tools_build.py +++ b/modules/mono/build_scripts/godot_tools_build.py @@ -87,7 +87,7 @@ def build(env_mono): target_filenames = ['GodotTools.dll', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'] if env_mono['target'] == 'debug': - target_filenames += ['GodotTools.pdb', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'GodotTools.Core.dll'] + target_filenames += ['GodotTools.pdb', 'GodotTools.BuildLogger.pdb', 'GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb'] targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] @@ -102,6 +102,10 @@ def build_project_editor_only(env_mono): editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools') target_filenames = ['GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'] + + if env_mono['target'] == 'debug': + target_filenames += ['GodotTools.ProjectEditor.pdb', 'GodotTools.Core.pdb'] + targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] cmd = env_mono.CommandNoCache(targets, [], build_godot_tools_project_editor, module_dir=os.getcwd()) diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py index 9f549a10ed..d1529a64d2 100644 --- a/modules/mono/build_scripts/solution_builder.py +++ b/modules/mono/build_scripts/solution_builder.py @@ -108,10 +108,14 @@ def find_msbuild_windows(env): if not mono_root: raise RuntimeError('Cannot find mono root directory') - framework_path = os.path.join(mono_root, 'lib', 'mono', '4.5') mono_bin_dir = os.path.join(mono_root, 'bin') msbuild_mono = os.path.join(mono_bin_dir, 'msbuild.bat') + msbuild_tools_path = find_msbuild_tools_path_reg() + + if msbuild_tools_path: + return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), {}) + if os.path.isfile(msbuild_mono): # The (Csc/Vbc/Fsc)ToolExe environment variables are required when # building with Mono's MSBuild. They must point to the batch files @@ -121,12 +125,7 @@ def find_msbuild_windows(env): 'VbcToolExe': os.path.join(mono_bin_dir, 'vbc.bat'), 'FscToolExe': os.path.join(mono_bin_dir, 'fsharpc.bat') } - return (msbuild_mono, framework_path, mono_msbuild_env) - - msbuild_tools_path = find_msbuild_tools_path_reg() - - if msbuild_tools_path: - return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), framework_path, {}) + return (msbuild_mono, mono_msbuild_env) return None @@ -172,7 +171,6 @@ def build_solution(env, solution_path, build_config, extra_msbuild_args=[]): global verbose verbose = env['verbose'] - framework_path = '' msbuild_env = os.environ.copy() # Needed when running from Developer Command Prompt for VS @@ -185,8 +183,7 @@ def build_solution(env, solution_path, build_config, extra_msbuild_args=[]): if msbuild_info is None: raise RuntimeError('Cannot find MSBuild executable') msbuild_path = msbuild_info[0] - framework_path = msbuild_info[1] - msbuild_env.update(msbuild_info[2]) + msbuild_env.update(msbuild_info[1]) else: msbuild_path = find_msbuild_unix('msbuild') if msbuild_path is None: @@ -212,7 +209,6 @@ def build_solution(env, solution_path, build_config, extra_msbuild_args=[]): # Build solution msbuild_args = [solution_path, '/p:Configuration=' + build_config] - msbuild_args += ['/p:FrameworkPathOverride=' + framework_path] if framework_path else [] msbuild_args += extra_msbuild_args run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name='msbuild') diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp new file mode 100644 index 0000000000..71ccdb7aab --- /dev/null +++ b/modules/mono/class_db_api_json.cpp @@ -0,0 +1,242 @@ +/*************************************************************************/ +/* class_db_api_json.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "class_db_api_json.h" + +#include "core/io/json.h" +#include "core/os/file_access.h" +#include "core/project_settings.h" +#include "core/version.h" + +void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { + Dictionary classes_dict; + + List<StringName> names; + + const StringName *k = NULL; + + while ((k = ClassDB::classes.next(k))) { + + names.push_back(*k); + } + //must be alphabetically sorted for hash to compute + names.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + + ClassDB::ClassInfo *t = ClassDB::classes.getptr(E->get()); + ERR_FAIL_COND(!t); + if (t->api != p_api || !t->exposed) + continue; + + Dictionary class_dict; + classes_dict[t->name] = class_dict; + + class_dict["inherits"] = t->inherits; + + { //methods + + List<StringName> snames; + + k = NULL; + + while ((k = t->method_map.next(k))) { + + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + Array methods; + + for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + Dictionary method_dict; + methods.push_back(method_dict); + + MethodBind *mb = t->method_map[F->get()]; + method_dict["name"] = mb->get_name(); + method_dict["argument_count"] = mb->get_argument_count(); + method_dict["return_type"] = mb->get_argument_type(-1); + + Array arguments; + method_dict["arguments"] = arguments; + + for (int i = 0; i < mb->get_argument_count(); i++) { + Dictionary argument_dict; + arguments.push_back(argument_dict); + const PropertyInfo info = mb->get_argument_info(i); + argument_dict["type"] = info.type; + argument_dict["name"] = info.name; + argument_dict["hint"] = info.hint; + argument_dict["hint_string"] = info.hint_string; + } + + method_dict["default_argument_count"] = mb->get_default_argument_count(); + + Array default_arguments; + method_dict["default_arguments"] = default_arguments; + + for (int i = 0; i < mb->get_default_argument_count(); i++) { + Dictionary default_argument_dict; + default_arguments.push_back(default_argument_dict); + //hash should not change, i hope for tis + Variant da = mb->get_default_argument(i); + default_argument_dict["value"] = da; + } + + method_dict["hint_flags"] = mb->get_hint_flags(); + } + + if (!methods.empty()) { + class_dict["methods"] = methods; + } + } + + { //constants + + List<StringName> snames; + + k = NULL; + + while ((k = t->constant_map.next(k))) { + + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + Array constants; + + for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + Dictionary constant_dict; + constants.push_back(constant_dict); + + constant_dict["name"] = F->get(); + constant_dict["value"] = t->constant_map[F->get()]; + } + + if (!constants.empty()) { + class_dict["constants"] = constants; + } + } + + { //signals + + List<StringName> snames; + + k = NULL; + + while ((k = t->signal_map.next(k))) { + + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + Array signals; + + for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + Dictionary signal_dict; + signals.push_back(signal_dict); + + MethodInfo &mi = t->signal_map[F->get()]; + signal_dict["name"] = F->get(); + + Array arguments; + signal_dict["arguments"] = arguments; + for (int i = 0; i < mi.arguments.size(); i++) { + Dictionary argument_dict; + arguments.push_back(argument_dict); + argument_dict["type"] = mi.arguments[i].type; + } + } + + if (!signals.empty()) { + class_dict["signals"] = signals; + } + } + + { //properties + + List<StringName> snames; + + k = NULL; + + while ((k = t->property_setget.next(k))) { + + snames.push_back(*k); + } + + snames.sort_custom<StringName::AlphCompare>(); + + Array properties; + + for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { + Dictionary property_dict; + properties.push_back(property_dict); + + ClassDB::PropertySetGet *psg = t->property_setget.getptr(F->get()); + + property_dict["name"] = F->get(); + property_dict["setter"] = psg->setter; + property_dict["getter"] = psg->getter; + } + + if (!properties.empty()) { + class_dict["property_setget"] = properties; + } + } + + Array property_list; + + //property list + for (List<PropertyInfo>::Element *F = t->property_list.front(); F; F = F->next()) { + Dictionary property_dict; + property_list.push_back(property_dict); + + property_dict["name"] = F->get().name; + property_dict["type"] = F->get().type; + property_dict["hint"] = F->get().hint; + property_dict["hint_string"] = F->get().hint_string; + property_dict["usage"] = F->get().usage; + } + + if (!property_list.empty()) { + class_dict["property_list"] = property_list; + } + } + + FileAccessRef f = FileAccess::open(p_output_file, FileAccess::WRITE); + ERR_FAIL_COND(!f); + f->store_string(JSON::print(classes_dict, /*indent: */ "\t")); + f->close(); + + print_line(String() + "ClassDB API JSON written to: " + ProjectSettings::get_singleton()->globalize_path(p_output_file)); +} diff --git a/modules/mono/class_db_api_json.h b/modules/mono/class_db_api_json.h new file mode 100644 index 0000000000..0aa9c20930 --- /dev/null +++ b/modules/mono/class_db_api_json.h @@ -0,0 +1,39 @@ +/*************************************************************************/ +/* class_db_api_json.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef CLASS_DB_API_JSON_H +#define CLASS_DB_API_JSON_H + +#include "core/class_db.h" +#include "core/ustring.h" + +void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api); + +#endif // CLASS_DB_API_JSON_H diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index b5c91a8585..1add697997 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -44,6 +44,10 @@ #include "editor/editor_node.h" #endif +#ifdef DEBUG_METHODS_ENABLED +#include "class_db_api_json.h" +#endif + #include "editor/editor_internal_calls.h" #include "godotsharp_dirs.h" #include "mono_gd/gd_mono_class.h" @@ -98,6 +102,15 @@ Error CSharpLanguage::execute_file(const String &p_path) { void CSharpLanguage::init() { +#ifdef DEBUG_METHODS_ENABLED + if (OS::get_singleton()->get_cmdline_args().find("--class_db_to_json")) { + class_db_api_to_json("user://class_db_api.json", ClassDB::API_CORE); +#ifdef TOOLS_ENABLED + class_db_api_to_json("user://class_db_api_editor.json", ClassDB::API_EDITOR); +#endif + } +#endif + gdmono = memnew(GDMono); gdmono->initialize(); @@ -867,17 +880,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 +919,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 +970,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 +1219,9 @@ CSharpLanguage::CSharpLanguage() { scripts_metadata_invalidated = true; +#ifdef TOOLS_ENABLED godotsharp_editor = NULL; +#endif } CSharpLanguage::~CSharpLanguage() { @@ -2144,7 +2162,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 +2208,7 @@ void CSharpScript::_update_member_info_no_exports() { } } } +#endif bool CSharpScript::_update_exports() { @@ -2673,35 +2691,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); - GDMonoClass *base = script->script_class->get_parent_class(); + p_script->name = p_class->get_name(); + p_script->script_class = p_class; + p_script->native = p_native; - if (base != script->native) - script->base = base; + CRASH_COND(p_script->native == NULL); - script->valid = true; - script->tool = script->script_class->has_attribute(CACHED_CLASS(ToolAttribute)); + GDMonoClass *base = p_script->script_class->get_parent_class(); - if (!script->tool) { - GDMonoClass *nesting_class = script->script_class->get_nesting_class(); - script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); + if (base != p_script->native) + p_script->base = base; + + 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 +2739,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 +2752,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 { diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index d31a1c35d2..eb168f344d 100644 --- a/modules/mono/csharp_script.h +++ b/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> ¶ms); - 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); diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index 7cf58b6755..4f21871f1a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -91,13 +91,11 @@ namespace GodotTools.ProjectEditor var coreApiRef = root.AddItem("Reference", CoreApiProjectName); coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll")); - coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProjectName + ".dll")); coreApiRef.AddMetadata("Private", "False"); var editorApiRef = root.AddItem("Reference", EditorApiProjectName); editorApiRef.Condition = " '$(Configuration)' == 'Tools' "; editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll")); - editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProjectName + ".dll")); editorApiRef.AddMetadata("Private", "False"); GenAssemblyInfoFile(root, dir, name); diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 22cf89695d..1edc426e00 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -1,6 +1,8 @@ using GodotTools.Core; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; using DotNet.Globbing; using Microsoft.Build.Construction; @@ -12,6 +14,8 @@ namespace GodotTools.ProjectEditor { var dir = Directory.GetParent(projectPath).FullName; var root = ProjectRootElement.Open(projectPath); + Debug.Assert(root != null); + var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\"); if (root.AddItemChecked(itemType, normalizedInclude)) @@ -23,7 +27,8 @@ namespace GodotTools.ProjectEditor string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories); // We want relative paths - for (int i = 0; i < files.Length; i++) { + for (int i = 0; i < files.Length; i++) + { files[i] = files[i].RelativeToPath(rootDirectory); } @@ -35,7 +40,7 @@ namespace GodotTools.ProjectEditor var result = new List<string>(); var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs"); - GlobOptions globOptions = new GlobOptions(); + var globOptions = new GlobOptions(); globOptions.Evaluation.CaseInsensitive = false; var root = ProjectRootElement.Open(projectPath); @@ -68,5 +73,103 @@ namespace GodotTools.ProjectEditor return result.ToArray(); } + + /// Simple function to make sure the Api assembly references are configured correctly + public static void FixApiHintPath(string projectPath) + { + var root = ProjectRootElement.Open(projectPath); + Debug.Assert(root != null); + + bool dirty = false; + + void AddPropertyIfNotPresent(string name, string condition, string value) + { + if (root.PropertyGroups + .Any(g => g.Condition == string.Empty || g.Condition == condition && + g.Properties + .Any(p => p.Name == name && + p.Value == value && + (p.Condition == condition || g.Condition == condition)))) + { + return; + } + + root.AddProperty(name, value).Condition = condition; + dirty = true; + } + + AddPropertyIfNotPresent(name: "ApiConfiguration", + condition: " '$(Configuration)' != 'Release' ", + value: "Debug"); + AddPropertyIfNotPresent(name: "ApiConfiguration", + condition: " '$(Configuration)' == 'Release' ", + value: "Release"); + + void SetReferenceHintPath(string referenceName, string condition, string hintPath) + { + foreach (var itemGroup in root.ItemGroups.Where(g => + g.Condition == string.Empty || g.Condition == condition)) + { + var references = itemGroup.Items.Where(item => + item.ItemType == "Reference" && + item.Include == referenceName && + (item.Condition == condition || itemGroup.Condition == condition)); + + var referencesWithHintPath = references.Where(reference => + reference.Metadata.Any(m => m.Name == "HintPath")); + + if (referencesWithHintPath.Any(reference => reference.Metadata + .Any(m => m.Name == "HintPath" && m.Value == hintPath))) + { + // Found a Reference item with the right HintPath + return; + } + + var referenceWithHintPath = referencesWithHintPath.FirstOrDefault(); + if (referenceWithHintPath != null) + { + // Found a Reference item with a wrong HintPath + foreach (var metadata in referenceWithHintPath.Metadata.ToList() + .Where(m => m.Name == "HintPath")) + { + // Safe to remove as we duplicate with ToList() to loop + referenceWithHintPath.RemoveChild(metadata); + } + + referenceWithHintPath.AddMetadata("HintPath", hintPath); + dirty = true; + return; + } + + var referenceWithoutHintPath = references.FirstOrDefault(); + if (referenceWithoutHintPath != null) + { + // Found a Reference item without a HintPath + referenceWithoutHintPath.AddMetadata("HintPath", hintPath); + dirty = true; + return; + } + } + + // Found no Reference item at all. Add it. + root.AddItem("Reference", referenceName).Condition = condition; + dirty = true; + } + + const string coreProjectName = "GodotSharp"; + const string editorProjectName = "GodotSharpEditor"; + + const string coreCondition = ""; + const string editorCondition = " '$(Configuration)' == 'Tools' "; + + var coreHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{coreProjectName}.dll"; + var editorHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{editorProjectName}.dll"; + + SetReferenceHintPath(coreProjectName, coreCondition, coreHintPath); + SetReferenceHintPath(editorProjectName, editorCondition, editorHintPath); + + if (dirty) + root.Save(); + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln new file mode 100644 index 0000000000..6f7d44bec2 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -0,0 +1,35 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.ProjectEditor", "GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools", "GodotTools\GodotTools.csproj", "{27B00618-A6F2-4828-B922-05CAEB08C286}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotTools.Core\GodotTools.Core.csproj", "{639E48BD-44E5-4091-8EDD-22D36DC0768D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.Build.0 = Release|Any CPU + {27B00618-A6F2-4828-B922-05CAEB08C286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27B00618-A6F2-4828-B922-05CAEB08C286}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27B00618-A6F2-4828-B922-05CAEB08C286}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27B00618-A6F2-4828-B922-05CAEB08C286}.Release|Any CPU.Build.0 = Release|Any CPU + {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {639E48BD-44E5-4091-8EDD-22D36DC0768D}.Release|Any CPU.Build.0 = Release|Any CPU + {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index a0d14c43c9..f0068385f4 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -172,7 +172,7 @@ namespace GodotTools.Build if (outputArray.Count == 0) return string.Empty; - var lines = outputArray[1].Split('\n'); + var lines = outputArray[0].Split('\n'); foreach (string line in lines) { diff --git a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs b/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs index 0426f0ac5a..e216d30462 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs @@ -11,11 +11,11 @@ namespace GodotTools { public static class CSharpProject { - public static string GenerateGameProject(string dir, string name, IEnumerable<string> files = null) + public static string GenerateGameProject(string dir, string name) { try { - return ProjectGenerator.GenGameProject(dir, name, files); + return ProjectGenerator.GenGameProject(dir, name, compileItems: new string[] { }); } catch (Exception e) { @@ -32,6 +32,18 @@ namespace GodotTools ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include); } + public static void FixApiHintPath(string projectPath) + { + try + { + ProjectUtils.FixApiHintPath(projectPath); + } + catch (Exception e) + { + GD.PushError(e.ToString()); + } + } + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static ulong ConvertToTimestamp(this DateTime value) @@ -58,7 +70,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) { diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 955574d5fe..da439b8d6f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -26,6 +26,8 @@ namespace GodotTools private MonoDevelopInstance monoDevelopInstance; private MonoDevelopInstance visualStudioForMacInstance; + private WeakReference<GodotSharpExport> exportPluginWeak; + public MonoBottomPanel MonoBottomPanel { get; private set; } private bool CreateProjectSolution() @@ -464,6 +466,9 @@ namespace GodotTools { // Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized. CallDeferred(nameof(_MakeApiSolutionsIfNeeded)); + + // Make sure the existing project has Api assembly references configured correctly + CSharpProject.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); } else { @@ -513,11 +518,27 @@ namespace GodotTools }); // Export plugin - AddExportPlugin(new GodotSharpExport()); + var exportPlugin = new GodotSharpExport(); + AddExportPlugin(exportPlugin); + exportPluginWeak = new WeakReference<GodotSharpExport>(exportPlugin); GodotSharpBuilds.Initialize(); } + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (exportPluginWeak.TryGetTarget(out var exportPlugin)) + { + // We need to dispose our export plugin before the editor destroys EditorSettings. + // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid + // will be freed after EditorSettings already was, and its device polling thread + // will try to access the EditorSettings singleton, resulting in null dereferencing. + exportPlugin.Dispose(); + } + } + public void OnBeforeSerialize() { } diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 1a440e5ced..45037bf637 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/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; diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp index 75b2dfce9a..6d85f55b97 100644 --- a/modules/mono/glue/base_object_glue.cpp +++ b/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() { diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 7ae991eeaa..06fbae019c 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/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() { diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 8e63ef3563..761c7f6fcb 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -46,20 +46,6 @@ bool GDMonoAssembly::in_preload = false; Vector<String> GDMonoAssembly::search_dirs; -static String _get_expected_api_build_config() { -#ifdef TOOLS_ENABLED - return "Debug"; -#else - -#ifdef DEBUG_ENABLED - return "Debug"; -#else - return "Release"; -#endif - -#endif -} - void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) { String framework_dir; @@ -81,11 +67,14 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir()); } - String api_config = p_custom_config.empty() ? _get_expected_api_build_config() : - (p_custom_config == "Release" ? "Release" : "Debug"); - r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config)); + if (p_custom_config.empty()) { + r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); + } else { + String api_config = p_custom_config == "Release" ? "Release" : "Debug"; + r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config)); + } - r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); + r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir()); r_search_dirs.push_back(OS::get_singleton()->get_resource_dir()); r_search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir()); diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index 6e431f51e7..20863b1afe 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/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 diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h index 69edf4deb7..ca25bc09f7 100644 --- a/modules/mono/utils/path_utils.h +++ b/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 diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 877122985d..2b014c2a45 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -44,7 +44,7 @@ int sfind(const String &p_text, int p_from) { int src_len = 2; int len = p_text.length(); - if (src_len == 0 || len == 0) + if (len == 0) return -1; const CharType *src = p_text.c_str(); |