Re-write mono module editor code in C#

Make the build system automatically build the C# Api assemblies to be shipped with the editor.
Make the editor, editor player and debug export templates use Api assemblies built with debug symbols.
Always run MSBuild to build the editor tools and Api assemblies when building Godot.
Several bugs fixed related to assembly hot reloading and restoring state.
Fix StringExtensions internal calls not being registered correctly, resulting in MissingMethodException.
This commit is contained in:
Ignacio Etcheverry 2019-07-03 09:44:53 +02:00
parent 7b569e91c0
commit 270af6fa08
93 changed files with 5694 additions and 4122 deletions

View file

@ -70,7 +70,7 @@ bool Reference::reference() {
if (get_script_instance()) {
get_script_instance()->refcount_incremented();
}
if (instance_binding_count > 0) {
if (instance_binding_count > 0 && !ScriptServer::are_languages_finished()) {
for (int i = 0; i < MAX_SCRIPT_INSTANCE_BINDINGS; i++) {
if (_script_instance_bindings[i]) {
ScriptServer::get_language(i)->refcount_incremented_instance_binding(this);
@ -91,7 +91,7 @@ bool Reference::unreference() {
bool script_ret = get_script_instance()->refcount_decremented();
die = die && script_ret;
}
if (instance_binding_count > 0) {
if (instance_binding_count > 0 && !ScriptServer::are_languages_finished()) {
for (int i = 0; i < MAX_SCRIPT_INSTANCE_BINDINGS; i++) {
if (_script_instance_bindings[i]) {
bool script_ret = ScriptServer::get_language(i)->refcount_decremented_instance_binding(this);

View file

@ -1579,6 +1579,8 @@ void EditorSettings::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_recent_dirs"), &EditorSettings::get_recent_dirs);
ADD_SIGNAL(MethodInfo("settings_changed"));
BIND_CONSTANT(NOTIFICATION_EDITOR_SETTINGS_CHANGED);
}
EditorSettings::EditorSettings() {

View file

@ -1,5 +1,8 @@
#!/usr/bin/env python
import build_scripts.tls_configure as tls_configure
import build_scripts.mono_configure as mono_configure
Import('env')
Import('env_modules')
@ -26,27 +29,36 @@ if env_mono['mono_glue']:
import os.path
if not os.path.isfile('glue/mono_glue.gen.cpp'):
raise RuntimeError('Missing mono glue sources. Did you forget to generate them?')
raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?")
if env_mono['tools'] or env_mono['target'] != 'release':
env_mono.Append(CPPDEFINES=['GD_MONO_HOT_RELOAD'])
# Configure Thread Local Storage
import build_scripts.tls_configure as tls_configure
conf = Configure(env_mono)
tls_configure.configure(conf)
env_mono = conf.Finish()
# Configure Mono
import build_scripts.mono_configure as mono_configure
mono_configure.configure(env, env_mono)
# Build GodotSharpTools
# Build Godot API solution
import build_scripts.godotsharptools_build as godotsharptools_build
if env_mono['tools'] and env_mono['mono_glue']:
import build_scripts.api_solution_build as api_solution_build
api_solution_build.build(env_mono)
godotsharptools_build.build(env_mono)
# Build GodotTools
if env_mono['tools']:
import build_scripts.godot_tools_build as godot_tools_build
if env_mono['mono_glue']:
godot_tools_build.build(env_mono)
else:
# Building without the glue sources so the Godot API solution may be missing.
# GodotTools depends on the Godot API solution. As such, we will only build
# GodotTools.ProjectEditor which doesn't depend on the Godot API solution and
# is required by the bindings generator in order to be able to generated it.
godot_tools_build.build_project_editor_only(env_mono)

0
modules/mono/__init__.py Normal file
View file

View file

@ -0,0 +1,66 @@
# Build the Godot API solution
import os
from SCons.Script import Dir
def build_api_solution(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
module_dir = env['module_dir']
solution_path = os.path.join(module_dir, 'glue/Managed/Generated/GodotSharp.sln')
if not os.path.isfile(solution_path):
raise RuntimeError("Godot API solution not found. Did you forget to run '--generate-mono-glue'?")
build_config = env['solution_build_config']
extra_msbuild_args = ['/p:NoWarn=1591'] # Ignore missing documentation warnings
from .solution_builder import build_solution
build_solution(env, solution_path, build_config, extra_msbuild_args=extra_msbuild_args)
# Copy targets
core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharp', 'bin', build_config))
editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharpEditor', 'bin', build_config))
dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
if not os.path.isdir(dst_dir):
assert not os.path.isfile(dst_dir)
os.makedirs(dst_dir)
def copy_target(target_path):
from shutil import copy
filename = os.path.basename(target_path)
src_path = os.path.join(core_src_dir, filename)
if not os.path.isfile(src_path):
src_path = os.path.join(editor_src_dir, filename)
copy(src_path, target_path)
for scons_target in target:
copy_target(str(scons_target))
def build(env_mono):
assert env_mono['tools']
target_filenames = [
'GodotSharp.dll', 'GodotSharp.pdb', 'GodotSharp.xml',
'GodotSharpEditor.dll', 'GodotSharpEditor.pdb', 'GodotSharpEditor.xml'
]
for build_config in ['Debug', 'Release']:
output_dir = Dir('#bin').abspath
editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', build_config)
targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames]
cmd = env_mono.CommandNoCache(targets, [], build_api_solution,
module_dir=os.getcwd(), solution_build_config=build_config)
env_mono.AlwaysBuild(cmd)

View file

@ -0,0 +1,108 @@
# Build GodotTools solution
import os
from SCons.Script import Dir
def build_godot_tools(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
module_dir = env['module_dir']
solution_path = os.path.join(module_dir, 'editor/GodotTools/GodotTools.sln')
build_config = 'Debug' if env['target'] == 'debug' else 'Release'
from . solution_builder import build_solution, nuget_restore
nuget_restore(env, solution_path)
build_solution(env, solution_path, build_config)
# Copy targets
solution_dir = os.path.abspath(os.path.join(solution_path, os.pardir))
src_dir = os.path.join(solution_dir, 'GodotTools', 'bin', build_config)
dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
if not os.path.isdir(dst_dir):
assert not os.path.isfile(dst_dir)
os.makedirs(dst_dir)
def copy_target(target_path):
from shutil import copy
filename = os.path.basename(target_path)
copy(os.path.join(src_dir, filename), target_path)
for scons_target in target:
copy_target(str(scons_target))
def build_godot_tools_project_editor(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
module_dir = env['module_dir']
project_name = 'GodotTools.ProjectEditor'
csproj_dir = os.path.join(module_dir, 'editor/GodotTools', project_name)
csproj_path = os.path.join(csproj_dir, project_name + '.csproj')
build_config = 'Debug' if env['target'] == 'debug' else 'Release'
from . solution_builder import build_solution, nuget_restore
# Make sure to restore NuGet packages in the project directory for the project to find it
nuget_restore(env, os.path.join(csproj_dir, 'packages.config'), '-PackagesDirectory',
os.path.join(csproj_dir, 'packages'))
build_solution(env, csproj_path, build_config)
# Copy targets
src_dir = os.path.join(csproj_dir, 'bin', build_config)
dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
if not os.path.isdir(dst_dir):
assert not os.path.isfile(dst_dir)
os.makedirs(dst_dir)
def copy_target(target_path):
from shutil import copy
filename = os.path.basename(target_path)
copy(os.path.join(src_dir, filename), target_path)
for scons_target in target:
copy_target(str(scons_target))
def build(env_mono):
assert env_mono['tools']
output_dir = Dir('#bin').abspath
editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')
editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', 'Debug')
source_filenames = ['GodotSharp.dll', 'GodotSharpEditor.dll']
sources = [os.path.join(editor_api_dir, filename) for filename in source_filenames]
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']
targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames]
cmd = env_mono.CommandNoCache(targets, sources, build_godot_tools, module_dir=os.getcwd())
env_mono.AlwaysBuild(cmd)
def build_project_editor_only(env_mono):
assert env_mono['tools']
output_dir = Dir('#bin').abspath
editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')
target_filenames = ['GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll']
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())
env_mono.AlwaysBuild(cmd)

View file

@ -1,10 +1,8 @@
import imp
import os
import os.path
import sys
import subprocess
from distutils.version import LooseVersion
from SCons.Script import Dir, Environment
if os.name == 'nt':
@ -58,6 +56,12 @@ def configure(env, env_mono):
mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0']
is_travis = os.environ.get('TRAVIS') == 'true'
if is_travis:
# Travis CI may have a Mono version lower than 5.12
env_mono.Append(CPPDEFINES=['NO_PENDING_EXCEPTIONS'])
if is_android and not env['android_arch'] in android_arch_dirs:
raise RuntimeError('This module does not support for the specified \'android_arch\': ' + env['android_arch'])
@ -83,9 +87,6 @@ def configure(env, env_mono):
print('Found Mono root directory: ' + mono_root)
mono_version = mono_root_try_find_mono_version(mono_root)
configure_for_mono_version(env_mono, mono_version)
mono_lib_path = os.path.join(mono_root, 'lib')
env.Append(LIBPATH=mono_lib_path)
@ -164,9 +165,6 @@ def configure(env, env_mono):
if mono_root:
print('Found Mono root directory: ' + mono_root)
mono_version = mono_root_try_find_mono_version(mono_root)
configure_for_mono_version(env_mono, mono_version)
mono_lib_path = os.path.join(mono_root, 'lib')
env.Append(LIBPATH=mono_lib_path)
@ -209,9 +207,6 @@ def configure(env, env_mono):
# TODO: Add option to force using pkg-config
print('Mono root directory not found. Using pkg-config instead')
mono_version = pkgconfig_try_find_mono_version()
configure_for_mono_version(env_mono, mono_version)
env.ParseConfig('pkg-config monosgen-2 --libs')
env_mono.ParseConfig('pkg-config monosgen-2 --cflags')
@ -401,17 +396,6 @@ def copy_mono_shared_libs(env, mono_root, target_mono_root_dir):
copy_if_exists(os.path.join(mono_root, 'lib', lib_file_name), target_mono_lib_dir)
def configure_for_mono_version(env, mono_version):
if mono_version is None:
if os.getenv('MONO_VERSION'):
mono_version = os.getenv('MONO_VERSION')
else:
raise RuntimeError("Mono JIT compiler version not found; specify one manually with the 'MONO_VERSION' environment variable")
print('Found Mono JIT compiler version: ' + str(mono_version))
if mono_version >= LooseVersion('5.12.0'):
env.Append(CPPDEFINES=['HAS_PENDING_EXCEPTIONS'])
def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext):
tmpenv = Environment()
tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
@ -421,36 +405,3 @@ def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext):
if name_found and os.path.isdir(os.path.join(hint_dir, '..', 'include', 'mono-2.0')):
return os.path.join(hint_dir, '..')
return ''
def pkgconfig_try_find_mono_version():
from compat import decode_utf8
lines = subprocess.check_output(['pkg-config', 'monosgen-2', '--modversion']).splitlines()
greater_version = None
for line in lines:
try:
version = LooseVersion(decode_utf8(line))
if greater_version is None or version > greater_version:
greater_version = version
except ValueError:
pass
return greater_version
def mono_root_try_find_mono_version(mono_root):
from compat import decode_utf8
mono_bin = os.path.join(mono_root, 'bin')
if os.path.isfile(os.path.join(mono_bin, 'mono')):
mono_binary = os.path.join(mono_bin, 'mono')
elif os.path.isfile(os.path.join(mono_bin, 'mono.exe')):
mono_binary = os.path.join(mono_bin, 'mono.exe')
else:
return None
output = subprocess.check_output([mono_binary, '--version'])
first_line = decode_utf8(output.splitlines()[0])
try:
return LooseVersion(first_line.split()[len('Mono JIT compiler version'.split())])
except (ValueError, IndexError):
return None

View file

@ -1,8 +1,8 @@
# Build GodotSharpTools solution
import os
from SCons.Script import Builder, Dir
verbose = False
def find_nuget_unix():
@ -131,12 +131,46 @@ def find_msbuild_windows(env):
return None
def mono_build_solution(source, target, env):
import subprocess
from shutil import copyfile
def run_command(command, args, env_override=None, name=None):
def cmd_args_to_str(cmd_args):
return ' '.join([arg if not ' ' in arg else '"%s"' % arg for arg in cmd_args])
sln_path = os.path.abspath(str(source[0]))
target_path = os.path.abspath(str(target[0]))
args = [command] + args
if name is None:
name = os.path.basename(command)
if verbose:
print("Running '%s': %s" % (name, cmd_args_to_str(args)))
import subprocess
try:
if env_override is None:
subprocess.check_call(args)
else:
subprocess.check_call(args, env=env_override)
except subprocess.CalledProcessError as e:
raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode))
def nuget_restore(env, *args):
global verbose
verbose = env['verbose']
# Find NuGet
nuget_path = find_nuget_windows(env) if os.name == 'nt' else find_nuget_unix()
if nuget_path is None:
raise RuntimeError('Cannot find NuGet executable')
print('NuGet path: ' + nuget_path)
# Do NuGet restore
run_command(nuget_path, ['restore'] + list(args), name='nuget restore')
def build_solution(env, solution_path, build_config, extra_msbuild_args=[]):
global verbose
verbose = env['verbose']
framework_path = ''
msbuild_env = os.environ.copy()
@ -175,64 +209,10 @@ def mono_build_solution(source, target, env):
print('MSBuild path: ' + msbuild_path)
# Find NuGet
nuget_path = find_nuget_windows(env) if os.name == 'nt' else find_nuget_unix()
if nuget_path is None:
raise RuntimeError('Cannot find NuGet executable')
print('NuGet path: ' + nuget_path)
# Do NuGet restore
try:
subprocess.check_call([nuget_path, 'restore', sln_path])
except subprocess.CalledProcessError:
raise RuntimeError('GodotSharpTools: NuGet restore failed')
# Build solution
build_config = 'Release'
msbuild_args = [solution_path, '/p:Configuration=' + build_config]
msbuild_args += ['/p:FrameworkPathOverride=' + framework_path] if framework_path else []
msbuild_args += extra_msbuild_args
msbuild_args = [
msbuild_path,
sln_path,
'/p:Configuration=' + build_config,
]
if framework_path:
msbuild_args += ['/p:FrameworkPathOverride=' + framework_path]
try:
subprocess.check_call(msbuild_args, env=msbuild_env)
except subprocess.CalledProcessError:
raise RuntimeError('GodotSharpTools: Build failed')
# Copy files
src_dir = os.path.abspath(os.path.join(sln_path, os.pardir, 'bin', build_config))
dst_dir = os.path.abspath(os.path.join(target_path, os.pardir))
asm_file = 'GodotSharpTools.dll'
if not os.path.isdir(dst_dir):
if os.path.exists(dst_dir):
raise RuntimeError('Target directory is a file')
os.makedirs(dst_dir)
copyfile(os.path.join(src_dir, asm_file), os.path.join(dst_dir, asm_file))
# Dependencies
copyfile(os.path.join(src_dir, "DotNet.Glob.dll"), os.path.join(dst_dir, "DotNet.Glob.dll"))
def build(env_mono):
if not env_mono['tools']:
return
output_dir = Dir('#bin').abspath
editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')
mono_sln_builder = Builder(action=mono_build_solution)
env_mono.Append(BUILDERS={'MonoBuildSolution': mono_sln_builder})
env_mono.MonoBuildSolution(
os.path.join(editor_tools_dir, 'GodotSharpTools.dll'),
'editor/GodotSharpTools/GodotSharpTools.sln'
)
run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name='msbuild')

View file

@ -42,9 +42,9 @@
#include "editor/bindings_generator.h"
#include "editor/csharp_project.h"
#include "editor/editor_node.h"
#include "editor/godotsharp_editor.h"
#endif
#include "editor/editor_internal_calls.h"
#include "godotsharp_dirs.h"
#include "mono_gd/gd_mono_class.h"
#include "mono_gd/gd_mono_marshal.h"
@ -65,8 +65,8 @@ static bool _create_project_solution_if_needed() {
if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) {
// A solution does not yet exist, create a new one
CRASH_COND(GodotSharpEditor::get_singleton() == NULL);
return GodotSharpEditor::get_singleton()->call("_create_project_solution");
CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == NULL);
return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution");
}
return true;
@ -96,14 +96,6 @@ Error CSharpLanguage::execute_file(const String &p_path) {
return OK;
}
#ifdef TOOLS_ENABLED
void gdsharp_editor_init_callback() {
EditorNode *editor = EditorNode::get_singleton();
editor->add_child(memnew(GodotSharpEditor(editor)));
}
#endif
void CSharpLanguage::init() {
gdmono = memnew(GDMono);
@ -114,14 +106,12 @@ void CSharpLanguage::init() {
#endif
#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED)
if (gdmono->get_editor_tools_assembly() != NULL) {
List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
BindingsGenerator::handle_cmdline_args(cmdline_args);
}
List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
BindingsGenerator::handle_cmdline_args(cmdline_args);
#endif
#ifdef TOOLS_ENABLED
EditorNode::add_init_callback(&gdsharp_editor_init_callback);
EditorNode::add_init_callback(&_editor_init_callback);
GLOBAL_DEF("mono/export/include_scripts_content", false);
#endif
@ -664,7 +654,7 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft
CRASH_COND(!Engine::get_singleton()->is_editor_hint());
#ifdef TOOLS_ENABLED
MonoReloadNode::get_singleton()->restart_reload_timer();
get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer");
#endif
#ifdef GD_MONO_HOT_RELOAD
@ -731,58 +721,93 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
SCOPED_MUTEX_LOCK(script_instances_mutex);
for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
if (elem->self()->get_path().is_resource_file()) {
// Cast to CSharpScript to avoid being erased by accident
scripts.push_back(Ref<CSharpScript>(elem->self()));
}
// Cast to CSharpScript to avoid being erased by accident
scripts.push_back(Ref<CSharpScript>(elem->self()));
}
}
List<Ref<CSharpScript> > to_reload;
// We need to keep reference instances alive during reloading
List<Ref<Reference> > ref_instances;
for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
CSharpScriptBinding &script_binding = E->value();
Reference *ref = Object::cast_to<Reference>(script_binding.owner);
if (ref) {
ref_instances.push_back(Ref<Reference>(ref));
}
}
// As scripts are going to be reloaded, must proceed without locking here
scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order
for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
Ref<CSharpScript> &script = E->get();
to_reload.push_back(script);
if (script->get_path().empty()) {
script->tied_class_name_for_reload = script->script_class->get_name();
script->tied_class_namespace_for_reload = script->script_class->get_namespace();
}
// Script::instances are deleted during managed object disposal, which happens on domain finalize.
// Only placeholders are kept. Therefore we need to keep a copy before that happens.
for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) {
script->pending_reload_instances.insert(F->get()->get_instance_id());
Object *obj = F->get();
script->pending_reload_instances.insert(obj->get_instance_id());
Reference *ref = Object::cast_to<Reference>(obj);
if (ref) {
ref_instances.push_back(Ref<Reference>(ref));
}
}
#ifdef TOOLS_ENABLED
for (Set<PlaceHolderScriptInstance *>::Element *F = script->placeholders.front(); F; F = F->next()) {
script->pending_reload_instances.insert(F->get()->get_owner()->get_instance_id());
Object *obj = F->get()->get_owner();
script->pending_reload_instances.insert(obj->get_instance_id());
Reference *ref = Object::cast_to<Reference>(obj);
if (ref) {
ref_instances.push_back(Ref<Reference>(ref));
}
}
#endif
// FIXME: What about references? Need to keep them alive if only managed code references them.
// Save state and remove script from instances
Map<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state;
while (script->instances.front()) {
Object *obj = script->instances.front()->get();
// Save instance info
CSharpScript::StateBackup state;
for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) {
Object *obj = F->get();
ERR_CONTINUE(!obj->get_script_instance());
// TODO: Proper state backup (Not only variants, serialize managed state of scripts)
obj->get_script_instance()->get_property_state(state.properties);
CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle;
if (gchandle.is_valid())
gchandle->release();
// Call OnBeforeSerialize
if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
obj->get_script_instance()->call_multilevel(string_names.on_before_serialize);
// Save instance info
CSharpScript::StateBackup state;
// TODO: Proper state backup (Not only variants, serialize managed state of scripts)
csi->get_properties_state_for_reloading(state.properties);
owners_map[obj->get_instance_id()] = state;
}
}
// After the state of all instances is saved, clear scripts and script instances
for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
Ref<CSharpScript> &script = E->get();
while (script->instances.front()) {
Object *obj = script->instances.front()->get();
obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload)
}
@ -825,26 +850,76 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
scr->pending_reload_state.erase(obj_id);
}
}
return;
}
for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
List<Ref<CSharpScript> > to_reload_state;
Ref<CSharpScript> scr = E->get();
for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
Ref<CSharpScript> script = E->get();
if (!script->get_path().empty()) {
#ifdef TOOLS_ENABLED
scr->exports_invalidated = true;
script->exports_invalidated = true;
#endif
scr->signals_invalidated = true;
scr->reload(p_soft_reload);
scr->update_exports();
script->signals_invalidated = true;
script->reload(p_soft_reload);
script->update_exports();
} 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);
if (!script_class) {
script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL);
}
if (!script_class) {
script_class = gdmono->get_class(class_namespace, class_name);
}
if (!script_class) {
// The class was removed, can't reload
script->pending_reload_instances.clear();
continue;
}
bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(script_class);
if (!obj_type) {
// The class no longer inherits Godot.Object, can't reload
script->pending_reload_instances.clear();
continue;
}
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;
}
String native_name = NATIVE_GDMONOCLASS_NAME(script->native);
{
for (Set<ObjectID>::Element *F = scr->pending_reload_instances.front(); F; F = F->next()) {
for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) {
ObjectID obj_id = F->get();
Object *obj = ObjectDB::get_instance(obj_id);
if (!obj) {
scr->pending_reload_state.erase(obj_id);
script->pending_reload_state.erase(obj_id);
continue;
}
if (!ClassDB::is_parent_class(obj->get_class_name(), native_name)) {
// No longer inherits the same compatible type, can't reload
script->pending_reload_state.erase(obj_id);
continue;
}
@ -856,28 +931,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
// Non-placeholder script instances are removed in godot_icall_Object_Disposed.
CRASH_COND(!si->is_placeholder());
if (scr->is_tool() || ScriptServer::is_scripting_enabled()) {
if (script->is_tool() || ScriptServer::is_scripting_enabled()) {
// Replace placeholder with a script instance
CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id];
CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
// Backup placeholder script instance state before replacing it with a script instance
si->get_property_state(state_backup.properties);
ScriptInstance *script_instance = scr->instance_create(obj);
ScriptInstance *script_instance = script->instance_create(obj);
if (script_instance) {
scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
script->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
obj->set_script_instance(script_instance);
}
// TODO: Restore serialized state
for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
script_instance->set(G->get().first, G->get().second);
}
scr->pending_reload_state.erase(obj_id);
}
continue;
@ -887,19 +954,42 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
#endif
// Re-create script instance
obj->set_script(scr.get_ref_ptr()); // will create the script instance as well
obj->set_script(script.get_ref_ptr()); // will create the script instance as well
}
}
// TODO: Restore serialized state
to_reload_state.push_back(script);
}
for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
obj->get_script_instance()->set(G->get().first, G->get().second);
}
for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) {
Ref<CSharpScript> script = E->get();
scr->pending_reload_state.erase(obj_id);
for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) {
ObjectID obj_id = F->get();
Object *obj = ObjectDB::get_instance(obj_id);
if (!obj) {
script->pending_reload_state.erase(obj_id);
continue;
}
scr->pending_reload_instances.clear();
ERR_CONTINUE(!obj->get_script_instance());
// TODO: Restore serialized state
CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
obj->get_script_instance()->set(G->get().first, G->get().second);
}
// Call OnAfterDeserialization
CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance());
if (csi && csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize);
}
script->pending_reload_instances.clear();
}
#ifdef TOOLS_ENABLED
@ -964,12 +1054,12 @@ void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const
#ifdef TOOLS_ENABLED
Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) {
return GodotSharpEditor::get_singleton()->open_in_external_editor(p_script, p_line, p_col);
return (Error)(int)get_godotsharp_editor()->call("OpenInExternalEditor", p_script, p_line, p_col);
}
bool CSharpLanguage::overrides_external_editor() {
return GodotSharpEditor::get_singleton()->overrides_external_editor();
return get_godotsharp_editor()->call("OverridesExternalEditor");
}
#endif
@ -1027,6 +1117,34 @@ void CSharpLanguage::_on_scripts_domain_unloaded() {
scripts_metadata_invalidated = true;
}
#ifdef TOOLS_ENABLED
void CSharpLanguage::_editor_init_callback() {
register_editor_internal_calls();
// Initialize GodotSharpEditor
GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor");
CRASH_COND(editor_klass == NULL);
MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr());
CRASH_COND(mono_object == NULL);
MonoException *exc = NULL;
GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc);
UNHANDLED_EXCEPTION(exc);
EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(GDMonoMarshal::mono_object_to_variant(mono_object));
CRASH_COND(godotsharp_editor == NULL);
// Enable it as a plugin
EditorNode::add_editor_plugin(godotsharp_editor);
godotsharp_editor->enable_plugin();
get_singleton()->godotsharp_editor = godotsharp_editor;
}
#endif
void CSharpLanguage::set_language_index(int p_idx) {
ERR_FAIL_COND(lang_idx != -1);
@ -1084,6 +1202,8 @@ CSharpLanguage::CSharpLanguage() {
lang_idx = -1;
scripts_metadata_invalidated = true;
godotsharp_editor = NULL;
}
CSharpLanguage::~CSharpLanguage() {
@ -1139,6 +1259,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
r_script_binding.type_name = type_name;
r_script_binding.wrapper_class = type_class; // cache
r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
r_script_binding.owner = p_object;
// Tie managed to unmanaged
Reference *ref = Object::cast_to<Reference>(p_object);
@ -1223,6 +1344,9 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
if (!script_binding.inited)
return;
if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
// The reference count was increased after the managed side was the only one referencing our owner.
// This means the owner is being referenced again by the unmanaged side,
@ -1247,14 +1371,17 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
CRASH_COND(!ref_owner);
#endif
int refcount = ref_owner->reference_get_count();
void *data = p_object->get_script_instance_binding(get_language_index());
CRASH_COND(!data);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
int refcount = ref_owner->reference_get_count();
if (!script_binding.inited)
return refcount == 0;
if (refcount == 1 && gchandle.is_valid() && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
// If owner owner is no longer referenced by the unmanaged side,
// the managed instance takes responsibility of deleting the owner when GCed.
@ -1417,6 +1544,31 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
return false;
}
void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state) {
List<PropertyInfo> pinfo;
get_property_list(&pinfo);
for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
Pair<StringName, Variant> state_pair;
state_pair.first = E->get().name;
ManagedType managedType;
GDMonoField *field = script->script_class->get_field(state_pair.first);
if (!field)
continue; // Properties ignored. We get the property baking fields instead.
managedType = field->get_type();
if (GDMonoMarshal::managed_to_variant_type(managedType) != Variant::NIL) { // If we can marshal it
if (get(state_pair.first, state_pair.second)) {
r_state.push_back(state_pair);
}
}
}
}
void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) {
@ -1614,17 +1766,18 @@ MonoObject *CSharpInstance::_internal_new_managed() {
ERR_FAIL_NULL_V(owner, NULL);
ERR_FAIL_COND_V(script.is_null(), NULL);
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script->script_class->get_mono_ptr());
MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr());
if (!mono_object) {
// Important to clear this before destroying the script instance here
script = Ref<CSharpScript>();
owner = NULL;
bool die = _unreference_owner_unsafe();
// Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug.
CRASH_COND(die == true);
owner = NULL;
ERR_EXPLAIN("Failed to allocate memory for the object");
ERR_FAIL_V(NULL);
}
@ -1940,7 +2093,16 @@ CSharpInstance::~CSharpInstance() {
CRASH_COND(data == NULL);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
CRASH_COND(!script_binding.inited);
if (!script_binding.inited) {
SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->get_language_bind_mutex());
if (!script_binding.inited) { // Other thread may have set it up
// Already had a binding that needs to be setup
CSharpLanguage::get_singleton()->setup_csharp_script_binding(script_binding, owner);
CRASH_COND(!script_binding.inited);
}
}
bool die = _unreference_owner_unsafe();
CRASH_COND(die == true); // The "instance binding" should be holding a reference
@ -1984,6 +2146,52 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List
}
#endif
void CSharpScript::_update_member_info_no_exports() {
if (exports_invalidated) {
exports_invalidated = false;
member_info.clear();
GDMonoClass *top = script_class;
while (top && top != native) {
PropertyInfo prop_info;
bool exported;
const Vector<GDMonoField *> &fields = top->get_all_fields();
for (int i = fields.size() - 1; i >= 0; i--) {
GDMonoField *field = fields[i];
if (_get_member_export(field, /* inspect export: */ false, prop_info, exported)) {
StringName member_name = field->get_name();
member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
exported_members_defval_cache[member_name] = Variant();
}
}
const Vector<GDMonoProperty *> &properties = top->get_all_properties();
for (int i = properties.size() - 1; i >= 0; i--) {
GDMonoProperty *property = properties[i];
if (_get_member_export(property, /* inspect export: */ false, prop_info, exported)) {
StringName member_name = property->get_name();
member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
exported_members_defval_cache[member_name] = Variant();
}
}
top = top->get_parent_class();
}
}
}
bool CSharpScript::_update_exports() {
#ifdef TOOLS_ENABLED
@ -2008,7 +2216,7 @@ bool CSharpScript::_update_exports() {
// Here we create a temporary managed instance of the class to get the initial values
MonoObject *tmp_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr());
MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
if (!tmp_object) {
ERR_PRINT("Failed to allocate temporary MonoObject");
@ -2049,18 +2257,18 @@ bool CSharpScript::_update_exports() {
for (int i = fields.size() - 1; i >= 0; i--) {
GDMonoField *field = fields[i];
if (_get_member_export(field, prop_info, exported)) {
StringName name = field->get_name();
if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) {
StringName member_name = field->get_name();
if (exported) {
member_info[name] = prop_info;
member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
if (tmp_object) {
exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
}
} else {
member_info[name] = prop_info;
member_info[member_name] = prop_info;
}
}
}
@ -2070,25 +2278,25 @@ bool CSharpScript::_update_exports() {
for (int i = properties.size() - 1; i >= 0; i--) {
GDMonoProperty *property = properties[i];
if (_get_member_export(property, prop_info, exported)) {
StringName name = property->get_name();
if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) {
StringName member_name = property->get_name();
if (exported) {
member_info[name] = prop_info;
member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
if (tmp_object) {
MonoException *exc = NULL;
MonoObject *ret = property->get_value(tmp_object, &exc);
if (exc) {
exported_members_defval_cache[name] = Variant();
exported_members_defval_cache[member_name] = Variant();
GDMonoUtils::debug_print_unhandled_exception(exc);
} else {
exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(ret);
exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret);
}
}
} else {
member_info[name] = prop_info;
member_info[member_name] = prop_info;
}
}
}
@ -2197,7 +2405,7 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve
* Returns false if there was an error, otherwise true.
* If there was an error, r_prop_info and r_exported are not assigned any value.
*/
bool CSharpScript::_get_member_export(IMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported) {
bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) {
// Goddammit, C++. All I wanted was some nested functions.
#define MEMBER_FULL_QUALIFIED_NAME(m_member) \
@ -2222,26 +2430,30 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, PropertyInfo &
CRASH_NOW();
}
Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type);
if (!p_member->has_attribute(CACHED_CLASS(ExportAttribute))) {
r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
r_exported = false;
return true;
}
bool exported = p_member->has_attribute(CACHED_CLASS(ExportAttribute));
if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) {
GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member);
if (!property->has_getter()) {
ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
if (exported)
ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
}
if (!property->has_setter()) {
ERR_PRINTS("Set-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
if (exported)
ERR_PRINTS("Write-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
}
}
Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type);
if (!p_inspect_export || !exported) {
r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
r_exported = false;
return true;
}
MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute));
PropertyHint hint = PROPERTY_HINT_NONE;
@ -2463,9 +2675,9 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
// This method should not fail
CRASH_COND(!p_class);
CRASH_COND(p_class == NULL);
// TODO: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time
// 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();
@ -2479,6 +2691,20 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
if (base != script->native)
script->base = base;
script->valid = true;
script->tool = script->script_class->has_attribute(CACHED_CLASS(ToolAttribute));
if (!script->tool) {
GDMonoClass *nesting_class = script->script_class->get_nesting_class();
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();
}
#endif
#ifdef DEBUG_ENABLED
// For debug builds, we must fetch from all native base methods as well.
// Native base methods must be fetched before the current class.
@ -2507,6 +2733,7 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
}
script->load_script_signals(script->script_class, script->native);
script->_update_member_info_no_exports();
return script;
}
@ -2516,7 +2743,8 @@ bool CSharpScript::can_instance() const {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
if (get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted...
// Hack to lower the risk of attached scripts not being added to the C# project
if (!get_path().empty() && get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted...
if (_create_project_solution_if_needed()) {
CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(),
"Compile",
@ -2568,7 +2796,9 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount);
if (ctor == NULL) {
if (p_argcount == 0) {
ERR_PRINTS("Cannot create script instance because the class does not define a parameterless constructor: " + get_path());
String path = get_path();
ERR_PRINTS("Cannot create script instance. The class '" + script_class->get_full_name() +
"' does not define a parameterless constructor." + (path.empty() ? String() : ". Path: " + path));
}
ERR_EXPLAIN("Constructor not found");
@ -2610,7 +2840,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
/* STEP 2, INITIALIZE AND CONSTRUCT */
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr());
MonoObject *mono_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
if (!mono_object) {
// Important to clear this before destroying the script instance here
@ -2691,7 +2921,7 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
#endif
if (native) {
String native_name = native->get_name();
String native_name = NATIVE_GDMONOCLASS_NAME(native);
if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) {
if (ScriptDebugger::get_singleton()) {
CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'");
@ -2817,11 +3047,22 @@ Error CSharpScript::reload(bool p_keep_state) {
if (script_class) {
#ifdef DEBUG_ENABLED
print_verbose("Found class " + script_class->get_namespace() + "." + script_class->get_name() + " for script " + get_path());
print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
#endif
tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute));
if (!tool) {
GDMonoClass *nesting_class = script_class->get_nesting_class();
tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
}
#if TOOLS_ENABLED
if (!tool) {
tool = script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
}
#endif
native = GDMonoUtils::get_class_native_base(script_class);
CRASH_COND(native == NULL);
@ -3019,7 +3260,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
#endif
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() && mono_domain_get() == NULL) {
MonoDomain *domain = mono_domain_get();
if (Engine::get_singleton()->is_editor_hint() && domain == NULL) {
CRASH_COND(Thread::get_caller_id() == Thread::get_main_id());
@ -3027,8 +3269,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
// because this may be called by one of the editor's worker threads.
// Attach this thread temporarily to reload the script.
if (SCRIPTS_DOMAIN) {
MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN);
if (domain) {
MonoThread *mono_thread = mono_thread_attach(domain);
CRASH_COND(mono_thread == NULL);
script->reload();
mono_thread_detach(mono_thread);
@ -3128,5 +3370,7 @@ CSharpLanguage::StringNameCache::StringNameCache() {
_get_property_list = StaticCString::create("_get_property_list");
_notification = StaticCString::create("_notification");
_script_source = StaticCString::create("script/source");
on_before_serialize = StaticCString::create("OnBeforeSerialize");
on_after_deserialize = StaticCString::create("OnAfterDeserialize");
dotctor = StaticCString::create(".ctor");
}

View file

@ -41,6 +41,10 @@
#include "mono_gd/gd_mono_header.h"
#include "mono_gd/gd_mono_internals.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_plugin.h"
#endif
class CSharpScript;
class CSharpInstance;
class CSharpLanguage;
@ -92,6 +96,8 @@ class CSharpScript : public Script {
Set<ObjectID> pending_reload_instances;
Map<ObjectID, StateBackup> pending_reload_state;
StringName tied_class_name_for_reload;
StringName tied_class_namespace_for_reload;
#endif
String source;
@ -125,9 +131,10 @@ 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, PropertyInfo &r_prop_info, bool &r_exported);
bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported);
static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string);
#endif
@ -226,6 +233,8 @@ class CSharpInstance : public ScriptInstance {
MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const;
void get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state);
public:
MonoObject *get_mono_object() const;
@ -276,6 +285,7 @@ struct CSharpScriptBinding {
StringName type_name;
GDMonoClass *wrapper_class;
Ref<MonoGCHandle> gchandle;
Object *owner;
};
class CSharpLanguage : public ScriptLanguage {
@ -305,6 +315,8 @@ class CSharpLanguage : public ScriptLanguage {
StringName _notification;
StringName _script_source;
StringName dotctor; // .ctor
StringName on_before_serialize; // OnBeforeSerialize
StringName on_after_deserialize; // OnAfterDeserialize
StringNameCache();
};
@ -324,6 +336,12 @@ class CSharpLanguage : public ScriptLanguage {
friend class GDMono;
void _on_scripts_domain_unloaded();
#ifdef TOOLS_ENABLED
EditorPlugin *godotsharp_editor;
static void _editor_init_callback();
#endif
public:
StringNameCache string_names;
@ -336,6 +354,8 @@ public:
_FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; }
_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle);
static void release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle);

View file

@ -1,2 +0,0 @@
# nuget packages
packages

View file

@ -1,425 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using Microsoft.Build.Framework;
namespace GodotSharpTools.Build
{
public class BuildInstance : IDisposable
{
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode);
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static string godot_icall_BuildInstance_get_MSBuildPath();
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static string godot_icall_BuildInstance_get_MonoWindowsBinDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static bool godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows();
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static bool godot_icall_BuildInstance_get_PrintBuildOutput();
private static string GetMSBuildPath()
{
string msbuildPath = godot_icall_BuildInstance_get_MSBuildPath();
if (msbuildPath == null)
throw new FileNotFoundException("Cannot find the MSBuild executable.");
return msbuildPath;
}
private static string MonoWindowsBinDir
{
get
{
string monoWinBinDir = godot_icall_BuildInstance_get_MonoWindowsBinDir();
if (monoWinBinDir == null)
throw new FileNotFoundException("Cannot find the Windows Mono binaries directory.");
return monoWinBinDir;
}
}
private static bool UsingMonoMSBuildOnWindows
{
get
{
return godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows();
}
}
private static bool PrintBuildOutput
{
get
{
return godot_icall_BuildInstance_get_PrintBuildOutput();
}
}
private string solution;
private string config;
private Process process;
private int exitCode;
public int ExitCode { get { return exitCode; } }
public bool IsRunning { get { return process != null && !process.HasExited; } }
public BuildInstance(string solution, string config)
{
this.solution = solution;
this.config = config;
}
public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null)
{
List<string> customPropertiesList = new List<string>();
if (customProperties != null)
customPropertiesList.AddRange(customProperties);
string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList);
ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs);
bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput;
if (!redirectOutput) // TODO: or if stdout verbose
Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
startInfo.RedirectStandardOutput = redirectOutput;
startInfo.RedirectStandardError = redirectOutput;
startInfo.UseShellExecute = false;
if (UsingMonoMSBuildOnWindows)
{
// These environment variables are required for Mono's MSBuild to find the compilers.
// We use the batch files in Mono's bin directory to make sure the compilers are executed with mono.
string monoWinBinDir = MonoWindowsBinDir;
startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat"));
startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat"));
startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat"));
}
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
using (Process process = new Process())
{
process.StartInfo = startInfo;
process.Start();
if (redirectOutput)
{
process.BeginOutputReadLine();
process.BeginErrorReadLine();
}
process.WaitForExit();
exitCode = process.ExitCode;
}
return true;
}
public bool BuildAsync(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null)
{
if (process != null)
throw new InvalidOperationException("Already in use");
List<string> customPropertiesList = new List<string>();
if (customProperties != null)
customPropertiesList.AddRange(customProperties);
string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList);
ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs);
bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput;
if (!redirectOutput) // TODO: or if stdout verbose
Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
startInfo.RedirectStandardOutput = redirectOutput;
startInfo.RedirectStandardError = redirectOutput;
startInfo.UseShellExecute = false;
if (UsingMonoMSBuildOnWindows)
{
// These environment variables are required for Mono's MSBuild to find the compilers.
// We use the batch files in Mono's bin directory to make sure the compilers are executed with mono.
string monoWinBinDir = MonoWindowsBinDir;
startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat"));
startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat"));
startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat"));
}
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
process = new Process();
process.StartInfo = startInfo;
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(BuildProcess_Exited);
process.Start();
if (redirectOutput)
{
process.BeginOutputReadLine();
process.BeginErrorReadLine();
}
return true;
}
private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, List<string> customProperties)
{
string arguments = string.Format(@"""{0}"" /v:normal /t:Rebuild ""/p:{1}"" ""/l:{2},{3};{4}""",
solution,
"Configuration=" + config,
typeof(GodotBuildLogger).FullName,
loggerAssemblyPath,
loggerOutputDir
);
foreach (string customProperty in customProperties)
{
arguments += " /p:" + customProperty;
}
return arguments;
}
private void RemovePlatformVariable(StringDictionary environmentVariables)
{
// EnvironmentVariables is case sensitive? Seriously?
List<string> platformEnvironmentVariables = new List<string>();
foreach (string env in environmentVariables.Keys)
{
if (env.ToUpper() == "PLATFORM")
platformEnvironmentVariables.Add(env);
}
foreach (string env in platformEnvironmentVariables)
environmentVariables.Remove(env);
}
private void BuildProcess_Exited(object sender, System.EventArgs e)
{
exitCode = process.ExitCode;
godot_icall_BuildInstance_ExitCallback(solution, config, exitCode);
Dispose();
}
private static bool IsDebugMSBuildRequested()
{
return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1";
}
public void Dispose()
{
if (process != null)
{
process.Dispose();
process = null;
}
}
}
public class GodotBuildLogger : ILogger
{
public string Parameters { get; set; }
public LoggerVerbosity Verbosity { get; set; }
public void Initialize(IEventSource eventSource)
{
if (null == Parameters)
throw new LoggerException("Log directory was not set.");
string[] parameters = Parameters.Split(new[] { ';' });
string logDir = parameters[0];
if (String.IsNullOrEmpty(logDir))
throw new LoggerException("Log directory was not set.");
if (parameters.Length > 1)
throw new LoggerException("Too many parameters passed.");
string logFile = Path.Combine(logDir, "msbuild_log.txt");
string issuesFile = Path.Combine(logDir, "msbuild_issues.csv");
try
{
if (!Directory.Exists(logDir))
Directory.CreateDirectory(logDir);
this.logStreamWriter = new StreamWriter(logFile);
this.issuesStreamWriter = new StreamWriter(issuesFile);
}
catch (Exception ex)
{
if
(
ex is UnauthorizedAccessException
|| ex is ArgumentNullException
|| ex is PathTooLongException
|| ex is DirectoryNotFoundException
|| ex is NotSupportedException
|| ex is ArgumentException
|| ex is SecurityException
|| ex is IOException
)
{
throw new LoggerException("Failed to create log file: " + ex.Message);
}
else
{
// Unexpected failure
throw;
}
}
eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted);
eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted);
eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised);
eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised);
eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised);
eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished);
}
void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
{
string line = String.Format("{0}({1},{2}): error {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message);
if (e.ProjectFile.Length > 0)
line += string.Format(" [{0}]", e.ProjectFile);
WriteLine(line);
string errorLine = String.Format(@"error,{0},{1},{2},{3},{4},{5}",
e.File.CsvEscape(), e.LineNumber, e.ColumnNumber,
e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile.CsvEscape());
issuesStreamWriter.WriteLine(errorLine);
}
void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
{
string line = String.Format("{0}({1},{2}): warning {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, e.ProjectFile);
if (e.ProjectFile != null && e.ProjectFile.Length > 0)
line += string.Format(" [{0}]", e.ProjectFile);
WriteLine(line);
string warningLine = String.Format(@"warning,{0},{1},{2},{3},{4},{5}",
e.File.CsvEscape(), e.LineNumber, e.ColumnNumber,
e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty);
issuesStreamWriter.WriteLine(warningLine);
}
void eventSource_MessageRaised(object sender, BuildMessageEventArgs e)
{
// BuildMessageEventArgs adds Importance to BuildEventArgs
// Let's take account of the verbosity setting we've been passed in deciding whether to log the message
if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal))
|| (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal))
|| (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed))
)
{
WriteLineWithSenderAndMessage(String.Empty, e);
}
}
void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
{
// TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName
// To keep this log clean, this logger will ignore these events.
}
void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
{
WriteLine(e.Message);
indent++;
}
void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
{
indent--;
WriteLine(e.Message);
}
/// <summary>
/// Write a line to the log, adding the SenderName
/// </summary>
private void WriteLineWithSender(string line, BuildEventArgs e)
{
if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/))
{
// Well, if the sender name is MSBuild, let's leave it out for prettiness
WriteLine(line);
}
else
{
WriteLine(e.SenderName + ": " + line);
}
}
/// <summary>
/// Write a line to the log, adding the SenderName and Message
/// (these parameters are on all MSBuild event argument objects)
/// </summary>
private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e)
{
if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/))
{
// Well, if the sender name is MSBuild, let's leave it out for prettiness
WriteLine(line + e.Message);
}
else
{
WriteLine(e.SenderName + ": " + line + e.Message);
}
}
private void WriteLine(string line)
{
for (int i = indent; i > 0; i--)
{
logStreamWriter.Write("\t");
}
logStreamWriter.WriteLine(line);
}
public void Shutdown()
{
logStreamWriter.Close();
issuesStreamWriter.Close();
}
public bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity)
{
return this.Verbosity >= checkVerbosity;
}
private StreamWriter logStreamWriter;
private StreamWriter issuesStreamWriter;
private int indent;
}
}

View file

@ -1,75 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
namespace GodotSharpTools.Editor
{
public static class GodotSharpExport
{
public static void _ExportBegin(string[] features, bool debug, string path, int flags)
{
var featureSet = new HashSet<string>(features);
if (PlatformHasTemplateDir(featureSet))
{
string templateDirName = "data.mono";
if (featureSet.Contains("Windows"))
{
templateDirName += ".windows";
templateDirName += featureSet.Contains("64") ? ".64" : ".32";
}
else if (featureSet.Contains("X11"))
{
templateDirName += ".x11";
templateDirName += featureSet.Contains("64") ? ".64" : ".32";
}
else
{
throw new NotSupportedException("Target platform not supported");
}
templateDirName += debug ? ".release_debug" : ".release";
string templateDirPath = Path.Combine(GetTemplatesDir(), templateDirName);
if (!Directory.Exists(templateDirPath))
throw new FileNotFoundException("Data template directory not found");
string outputDir = new FileInfo(path).Directory.FullName;
string outputDataDir = Path.Combine(outputDir, GetDataDirName());
if (Directory.Exists(outputDataDir))
Directory.Delete(outputDataDir, recursive: true); // Clean first
Directory.CreateDirectory(outputDataDir);
foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1)));
}
foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories))
{
File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1)));
}
}
}
public static bool PlatformHasTemplateDir(HashSet<string> featureSet)
{
// OSX export templates are contained in a zip, so we place
// our custom template inside it and let Godot do the rest.
return !featureSet.Any(f => new[] {"OSX", "Android"}.Contains(f));
}
[MethodImpl(MethodImplOptions.InternalCall)]
extern static string GetTemplatesDir();
[MethodImpl(MethodImplOptions.InternalCall)]
extern static string GetDataDirName();
}
}

View file

@ -1,17 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharpTools", "GodotSharpTools.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}"
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
EndGlobalSection
EndGlobal

View file

@ -1,62 +0,0 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
namespace GodotSharpTools.Utils
{
public static class OS
{
[MethodImpl(MethodImplOptions.InternalCall)]
extern static string GetPlatformName();
const string HaikuName = "Haiku";
const string OSXName = "OSX";
const string ServerName = "Server";
const string UWPName = "UWP";
const string WindowsName = "Windows";
const string X11Name = "X11";
public static bool IsHaiku()
{
return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsOSX()
{
return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsServer()
{
return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsUWP()
{
return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsWindows()
{
return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsX11()
{
return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
static bool? IsUnixCache = null;
static readonly string[] UnixPlatforms = new string[] { HaikuName, OSXName, ServerName, X11Name };
public static bool IsUnix()
{
if (IsUnixCache.HasValue)
return IsUnixCache.Value;
string osName = GetPlatformName();
IsUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
return IsUnixCache.Value;
}
}
}

View file

@ -0,0 +1,356 @@
# Rider
.idea/
# Visual Studio Code
.vscode/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/

View file

@ -0,0 +1,186 @@
using System;
using System.IO;
using System.Security;
using Microsoft.Build.Framework;
using GodotTools.Core;
namespace GodotTools.BuildLogger
{
public class GodotBuildLogger : ILogger
{
public static readonly string AssemblyPath = Path.GetFullPath(typeof(GodotBuildLogger).Assembly.Location);
public string Parameters { get; set; }
public LoggerVerbosity Verbosity { get; set; }
public void Initialize(IEventSource eventSource)
{
if (null == Parameters)
throw new LoggerException("Log directory was not set.");
var parameters = Parameters.Split(new[] {';'});
string logDir = parameters[0];
if (string.IsNullOrEmpty(logDir))
throw new LoggerException("Log directory was not set.");
if (parameters.Length > 1)
throw new LoggerException("Too many parameters passed.");
string logFile = Path.Combine(logDir, "msbuild_log.txt");
string issuesFile = Path.Combine(logDir, "msbuild_issues.csv");
try
{
if (!Directory.Exists(logDir))
Directory.CreateDirectory(logDir);
logStreamWriter = new StreamWriter(logFile);
issuesStreamWriter = new StreamWriter(issuesFile);
}
catch (Exception ex)
{
if (ex is UnauthorizedAccessException
|| ex is ArgumentNullException
|| ex is PathTooLongException
|| ex is DirectoryNotFoundException
|| ex is NotSupportedException
|| ex is ArgumentException
|| ex is SecurityException
|| ex is IOException)
{
throw new LoggerException("Failed to create log file: " + ex.Message);
}
else
{
// Unexpected failure
throw;
}
}
eventSource.ProjectStarted += eventSource_ProjectStarted;
eventSource.TaskStarted += eventSource_TaskStarted;
eventSource.MessageRaised += eventSource_MessageRaised;
eventSource.WarningRaised += eventSource_WarningRaised;
eventSource.ErrorRaised += eventSource_ErrorRaised;
eventSource.ProjectFinished += eventSource_ProjectFinished;
}
void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
{
string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}";
if (e.ProjectFile.Length > 0)
line += $" [{e.ProjectFile}]";
WriteLine(line);
string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
$@"{e.Code.CsvEscape()},{e.Message.CsvEscape()},{e.ProjectFile.CsvEscape()}";
issuesStreamWriter.WriteLine(errorLine);
}
void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
{
string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}";
if (!string.IsNullOrEmpty(e.ProjectFile))
line += $" [{e.ProjectFile}]";
WriteLine(line);
string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber},{e.Code.CsvEscape()}," +
$@"{e.Message.CsvEscape()},{(e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty)}";
issuesStreamWriter.WriteLine(warningLine);
}
private void eventSource_MessageRaised(object sender, BuildMessageEventArgs e)
{
// BuildMessageEventArgs adds Importance to BuildEventArgs
// Let's take account of the verbosity setting we've been passed in deciding whether to log the message
if (e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal)
|| e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal)
|| e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed))
{
WriteLineWithSenderAndMessage(string.Empty, e);
}
}
private void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
{
// TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName
// To keep this log clean, this logger will ignore these events.
}
private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
{
WriteLine(e.Message);
indent++;
}
private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
{
indent--;
WriteLine(e.Message);
}
/// <summary>
/// Write a line to the log, adding the SenderName
/// </summary>
private void WriteLineWithSender(string line, BuildEventArgs e)
{
if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase))
{
// Well, if the sender name is MSBuild, let's leave it out for prettiness
WriteLine(line);
}
else
{
WriteLine(e.SenderName + ": " + line);
}
}
/// <summary>
/// Write a line to the log, adding the SenderName and Message
/// (these parameters are on all MSBuild event argument objects)
/// </summary>
private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e)
{
if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase))
{
// Well, if the sender name is MSBuild, let's leave it out for prettiness
WriteLine(line + e.Message);
}
else
{
WriteLine(e.SenderName + ": " + line + e.Message);
}
}
private void WriteLine(string line)
{
for (int i = indent; i > 0; i--)
{
logStreamWriter.Write("\t");
}
logStreamWriter.WriteLine(line);
}
public void Shutdown()
{
logStreamWriter.Close();
issuesStreamWriter.Close();
}
private bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity)
{
return Verbosity >= checkVerbosity;
}
private StreamWriter logStreamWriter;
private StreamWriter issuesStreamWriter;
private int indent;
}
}

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>GodotTools.BuildLogger</RootNamespace>
<AssemblyName>GodotTools.BuildLogger</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="GodotBuildLogger.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
<Project>{639e48bd-44e5-4091-8edd-22d36dc0768d}</Project>
<Name>GodotTools.Core</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("GodotTools.BuildLogger")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("Godot Engine contributors")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("6CE9A984-37B1-4F8A-8FE9-609F05F071B3")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>GodotTools.Core</RootNamespace>
<AssemblyName>GodotTools.Core</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="ProcessExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StringExtensions.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,38 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace GodotTools.Core
{
public static class ProcessExtensions
{
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default(CancellationToken))
{
var tcs = new TaskCompletionSource<bool>();
void ProcessExited(object sender, EventArgs e)
{
tcs.TrySetResult(true);
}
process.EnableRaisingEvents = true;
process.Exited += ProcessExited;
try
{
if (process.HasExited)
return;
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
{
await tcs.Task;
}
}
finally
{
process.Exited -= ProcessExited;
}
}
}
}

View file

@ -0,0 +1,26 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("GodotTools.Core")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("Godot Engine contributors")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

View file

@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace GodotSharpTools
namespace GodotTools.Core
{
public static class StringExtensions
{
@ -25,7 +26,7 @@ namespace GodotSharpTools
path = path.Replace('\\', '/');
string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
string[] parts = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim();
@ -37,18 +38,40 @@ namespace GodotSharpTools
public static bool IsAbsolutePath(this string path)
{
return path.StartsWith("/", StringComparison.Ordinal) ||
path.StartsWith("\\", StringComparison.Ordinal) ||
path.StartsWith(driveRoot, StringComparison.Ordinal);
path.StartsWith("\\", StringComparison.Ordinal) ||
path.StartsWith(driveRoot, StringComparison.Ordinal);
}
public static string CsvEscape(this string value, char delimiter = ',')
{
bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1;
bool hasSpecialChar = value.IndexOfAny(new char[] {'\"', '\n', '\r', delimiter}) != -1;
if (hasSpecialChar)
return "\"" + value.Replace("\"", "\"\"") + "\"";
return value;
}
public static string ToSafeDirName(this string dirName, bool allowDirSeparator)
{
var invalidChars = new List<string> {":", "*", "?", "\"", "<", ">", "|"};
if (allowDirSeparator)
{
// Directory separators are allowed, but disallow ".." to avoid going up the filesystem
invalidChars.Add("..");
}
else
{
invalidChars.Add("/");
}
string safeDirName = dirName.Replace("\\", "/").Trim();
foreach (string invalidChar in invalidChars)
safeDirName = safeDirName.Replace(invalidChar, "-");
return safeDirName;
}
}
}

View file

@ -0,0 +1,15 @@
namespace GodotTools
{
public static class ApiAssemblyNames
{
public const string SolutionName = "GodotSharp";
public const string Core = "GodotSharp";
public const string Editor = "GodotSharpEditor";
}
public enum ApiAssemblyType
{
Core,
Editor
}
}

View file

@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.IO;
namespace GodotTools.ProjectEditor
{
public static class ApiSolutionGenerator
{
public static void GenerateApiSolution(string solutionDir,
string coreProjDir, IEnumerable<string> coreCompileItems,
string editorProjDir, IEnumerable<string> editorCompileItems)
{
var solution = new DotNetSolution(ApiAssemblyNames.SolutionName);
solution.DirectoryPath = solutionDir;
// GodotSharp project
const string coreApiAssemblyName = ApiAssemblyNames.Core;
string coreGuid = ProjectGenerator.GenCoreApiProject(coreProjDir, coreCompileItems);
var coreProjInfo = new DotNetSolution.ProjectInfo
{
Guid = coreGuid,
PathRelativeToSolution = Path.Combine(coreApiAssemblyName, $"{coreApiAssemblyName}.csproj")
};
coreProjInfo.Configs.Add("Debug");
coreProjInfo.Configs.Add("Release");
solution.AddNewProject(coreApiAssemblyName, coreProjInfo);
// GodotSharpEditor project
const string editorApiAssemblyName = ApiAssemblyNames.Editor;
string editorGuid = ProjectGenerator.GenEditorApiProject(editorProjDir,
$"../{coreApiAssemblyName}/{coreApiAssemblyName}.csproj", editorCompileItems);
var editorProjInfo = new DotNetSolution.ProjectInfo();
editorProjInfo.Guid = editorGuid;
editorProjInfo.PathRelativeToSolution = Path.Combine(editorApiAssemblyName, $"{editorApiAssemblyName}.csproj");
editorProjInfo.Configs.Add("Debug");
editorProjInfo.Configs.Add("Release");
solution.AddNewProject(editorApiAssemblyName, editorProjInfo);
// Save solution
solution.Save();
}
}
}

View file

@ -0,0 +1,122 @@
using GodotTools.Core;
using System.Collections.Generic;
using System.IO;
namespace GodotTools.ProjectEditor
{
public class DotNetSolution
{
private string directoryPath;
private readonly Dictionary<string, ProjectInfo> projects = new Dictionary<string, ProjectInfo>();
public string Name { get; }
public string DirectoryPath
{
get => directoryPath;
set => directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value);
}
public class ProjectInfo
{
public string Guid;
public string PathRelativeToSolution;
public List<string> Configs = new List<string>();
}
public void AddNewProject(string name, ProjectInfo projectInfo)
{
projects[name] = projectInfo;
}
public bool HasProject(string name)
{
return projects.ContainsKey(name);
}
public ProjectInfo GetProjectInfo(string name)
{
return projects[name];
}
public bool RemoveProject(string name)
{
return projects.Remove(name);
}
public void Save()
{
if (!Directory.Exists(DirectoryPath))
throw new FileNotFoundException("The solution directory does not exist.");
string projectsDecl = string.Empty;
string slnPlatformsCfg = string.Empty;
string projPlatformsCfg = string.Empty;
bool isFirstProject = true;
foreach (var pair in projects)
{
string name = pair.Key;
ProjectInfo projectInfo = pair.Value;
if (!isFirstProject)
projectsDecl += "\n";
projectsDecl += string.Format(ProjectDeclaration,
name, projectInfo.PathRelativeToSolution.Replace("/", "\\"), projectInfo.Guid);
for (int i = 0; i < projectInfo.Configs.Count; i++)
{
string config = projectInfo.Configs[i];
if (i != 0 || !isFirstProject)
{
slnPlatformsCfg += "\n";
projPlatformsCfg += "\n";
}
slnPlatformsCfg += string.Format(SolutionPlatformsConfig, config);
projPlatformsCfg += string.Format(ProjectPlatformsConfig, projectInfo.Guid, config);
}
isFirstProject = false;
}
string solutionPath = Path.Combine(DirectoryPath, Name + ".sln");
string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg);
File.WriteAllText(solutionPath, content);
}
public DotNetSolution(string name)
{
Name = name;
}
const string SolutionTemplate =
@"Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
{0}
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
{1}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2}
EndGlobalSection
EndGlobal
";
const string ProjectDeclaration =
@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}""
EndProject";
const string SolutionPlatformsConfig =
@" {0}|Any CPU = {0}|Any CPU";
const string ProjectPlatformsConfig =
@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU
{{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU";
}
}

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>GodotSharpTools</RootNamespace>
<AssemblyName>GodotSharpTools</AssemblyName>
<RootNamespace>GodotTools.ProjectEditor</RootNamespace>
<AssemblyName>GodotTools.ProjectEditor</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
</PropertyGroup>
@ -21,7 +21,6 @@
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
@ -31,25 +30,35 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="Microsoft.Build" />
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL">
<HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
<!--
When building Godot with 'mono_glue=no' SCons will build this project alone instead of the
entire solution. $(SolutionDir) is not defined in that case, so we need to workaround that.
We make SCons restore the NuGet packages in the project directory instead in this case.
-->
<HintPath Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
<HintPath>$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
<HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> <!-- Are you happy CI? -->
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="StringExtensions.cs" />
<Compile Include="Build\BuildSystem.cs" />
<Compile Include="Editor\MonoDevelopInstance.cs" />
<Compile Include="Project\ProjectExtensions.cs" />
<Compile Include="Project\IdentifierUtils.cs" />
<Compile Include="Project\ProjectGenerator.cs" />
<Compile Include="Project\ProjectUtils.cs" />
<Compile Include="ApiAssembliesInfo.cs" />
<Compile Include="ApiSolutionGenerator.cs" />
<Compile Include="DotNetSolution.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils\OS.cs" />
<Compile Include="Editor\GodotSharpExport.cs" />
<Compile Include="IdentifierUtils.cs" />
<Compile Include="ProjectExtensions.cs" />
<Compile Include="ProjectGenerator.cs" />
<Compile Include="ProjectUtils.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
<Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
<Name>GodotTools.Core</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
</Project>

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace GodotSharpTools.Project
namespace GodotTools.ProjectEditor
{
public static class IdentifierUtils
{
@ -12,7 +12,7 @@ namespace GodotSharpTools.Project
if (string.IsNullOrEmpty(qualifiedIdentifier))
throw new ArgumentException($"{nameof(qualifiedIdentifier)} cannot be empty", nameof(qualifiedIdentifier));
string[] identifiers = qualifiedIdentifier.Split(new[] { '.' });
string[] identifiers = qualifiedIdentifier.Split('.');
for (int i = 0; i < identifiers.Length; i++)
{
@ -66,8 +66,6 @@ namespace GodotSharpTools.Project
if (identifierBuilder.Length > startIndex || @char == '_')
identifierBuilder.Append(@char);
break;
default:
break;
}
}
@ -97,14 +95,14 @@ namespace GodotSharpTools.Project
}
else
{
if (_doubleUnderscoreKeywords.Contains(value))
if (DoubleUnderscoreKeywords.Contains(value))
return true;
}
return _keywords.Contains(value);
return Keywords.Contains(value);
}
static HashSet<string> _doubleUnderscoreKeywords = new HashSet<string>
private static readonly HashSet<string> DoubleUnderscoreKeywords = new HashSet<string>
{
"__arglist",
"__makeref",
@ -112,7 +110,7 @@ namespace GodotSharpTools.Project
"__refvalue",
};
static HashSet<string> _keywords = new HashSet<string>
private static readonly HashSet<string> Keywords = new HashSet<string>
{
"as",
"do",

View file

@ -1,8 +1,9 @@
using GodotTools.Core;
using System;
using DotNet.Globbing;
using Microsoft.Build.Construction;
namespace GodotSharpTools.Project
namespace GodotTools.ProjectEditor
{
public static class ProjectExtensions
{

View file

@ -1,17 +1,19 @@
using GodotTools.Core;
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Construction;
namespace GodotSharpTools.Project
namespace GodotTools.ProjectEditor
{
public static class ProjectGenerator
{
public const string CoreApiProjectName = "GodotSharp";
public const string EditorApiProjectName = "GodotSharpEditor";
const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}";
const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}";
private const string CoreApiProjectName = "GodotSharp";
private const string EditorApiProjectName = "GodotSharpEditor";
private const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}";
private const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}";
public static string GenCoreApiProject(string dir, string[] compileItems)
public static string GenCoreApiProject(string dir, IEnumerable<string> compileItems)
{
string path = Path.Combine(dir, CoreApiProjectName + ".csproj");
@ -24,8 +26,8 @@ namespace GodotSharpTools.Project
mainGroup.SetProperty("BaseIntermediateOutputPath", "obj");
GenAssemblyInfoFile(root, dir, CoreApiProjectName,
new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]" },
new string[] { "System.Runtime.CompilerServices" });
new[] {"[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]"},
new[] {"System.Runtime.CompilerServices"});
foreach (var item in compileItems)
{
@ -37,7 +39,7 @@ namespace GodotSharpTools.Project
return CoreApiProjectGuid;
}
public static string GenEditorApiProject(string dir, string coreApiProjPath, string[] compileItems)
public static string GenEditorApiProject(string dir, string coreApiProjPath, IEnumerable<string> compileItems)
{
string path = Path.Combine(dir, EditorApiProjectName + ".csproj");
@ -64,7 +66,7 @@ namespace GodotSharpTools.Project
return EditorApiProjectGuid;
}
public static string GenGameProject(string dir, string name, string[] compileItems)
public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems)
{
string path = Path.Combine(dir, name + ".csproj");
@ -74,6 +76,8 @@ namespace GodotSharpTools.Project
mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)"));
mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj"));
mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)"));
mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'Release' ";
mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'Release' ";
var toolsGroup = root.AddPropertyGroup();
toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' ";
@ -86,11 +90,13 @@ namespace GodotSharpTools.Project
toolsGroup.AddProperty("ConsolePause", "false");
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");
@ -108,7 +114,6 @@ namespace GodotSharpTools.Project
public static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null)
{
string propertiesDir = Path.Combine(dir, "Properties");
if (!Directory.Exists(propertiesDir))
Directory.CreateDirectory(propertiesDir);
@ -124,12 +129,9 @@ namespace GodotSharpTools.Project
string assemblyLinesText = string.Empty;
if (assemblyLines != null)
{
foreach (var assemblyLine in assemblyLines)
assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
}
assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
string content = string.Format(assemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs");
@ -194,8 +196,8 @@ namespace GodotSharpTools.Project
}
}
private const string assemblyInfoTemplate =
@"using System.Reflection;{0}
private const string AssemblyInfoTemplate =
@"using System.Reflection;{0}
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.

View file

@ -1,9 +1,10 @@
using GodotTools.Core;
using System.Collections.Generic;
using System.IO;
using DotNet.Globbing;
using Microsoft.Build.Construction;
namespace GodotSharpTools.Project
namespace GodotTools.ProjectEditor
{
public static class ProjectUtils
{
@ -53,7 +54,7 @@ namespace GodotSharpTools.Project
var glob = Glob.Parse(normalizedInclude, globOptions);
// TODO Check somehow if path has no blog to avoid the following loop...
// TODO Check somehow if path has no blob to avoid the following loop...
foreach (var existingFile in existingFiles)
{

View file

@ -4,12 +4,12 @@ using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("GodotSharpTools")]
[assembly: AssemblyTitle("GodotTools.ProjectEditor")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("ignacio")]
[assembly: AssemblyCopyright("Godot Engine contributors")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DotNet.Glob" version="2.1.1" targetFramework="net45" />
</packages>

View file

@ -0,0 +1,172 @@
using GodotTools.Core;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using GodotTools.BuildLogger;
using GodotTools.Internals;
using GodotTools.Utils;
using Directory = System.IO.Directory;
namespace GodotTools.Build
{
public static class BuildSystem
{
private static string GetMsBuildPath()
{
string msbuildPath = MsBuildFinder.FindMsBuild();
if (msbuildPath == null)
throw new FileNotFoundException("Cannot find the MSBuild executable.");
return msbuildPath;
}
private static string MonoWindowsBinDir
{
get
{
string monoWinBinDir = Path.Combine(Internal.MonoWindowsInstallRoot, "bin");
if (!Directory.Exists(monoWinBinDir))
throw new FileNotFoundException("Cannot find the Windows Mono install bin directory.");
return monoWinBinDir;
}
}
private static Godot.EditorSettings EditorSettings =>
GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
private static bool UsingMonoMsBuildOnWindows
{
get
{
if (OS.IsWindows())
{
return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool")
== GodotSharpBuilds.BuildTool.MsBuildMono;
}
return false;
}
}
private static bool PrintBuildOutput =>
(bool) EditorSettings.GetSetting("mono/builds/print_build_output");
private static Process LaunchBuild(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
{
var customPropertiesList = new List<string>();
if (customProperties != null)
customPropertiesList.AddRange(customProperties);
string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList);
var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs);
bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput;
if (!redirectOutput || Godot.OS.IsStdoutVerbose())
Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
startInfo.RedirectStandardOutput = redirectOutput;
startInfo.RedirectStandardError = redirectOutput;
startInfo.UseShellExecute = false;
if (UsingMonoMsBuildOnWindows)
{
// These environment variables are required for Mono's MSBuild to find the compilers.
// We use the batch files in Mono's bin directory to make sure the compilers are executed with mono.
string monoWinBinDir = MonoWindowsBinDir;
startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat"));
startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat"));
startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat"));
}
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
var process = new Process {StartInfo = startInfo};
process.Start();
if (redirectOutput)
{
process.BeginOutputReadLine();
process.BeginErrorReadLine();
}
return process;
}
public static int Build(MonoBuildInfo monoBuildInfo)
{
return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration,
monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties);
}
public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo)
{
return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration,
monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties);
}
public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
{
using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
{
process.WaitForExit();
return process.ExitCode;
}
}
public static async Task<int> BuildAsync(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
{
using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
{
await process.WaitForExitAsync();
return process.ExitCode;
}
}
private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties)
{
string arguments = $@"""{solution}"" /v:normal /t:Rebuild ""/p:{"Configuration=" + config}"" " +
$@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}""";
foreach (string customProperty in customProperties)
{
arguments += " /p:" + customProperty;
}
return arguments;
}
private static void RemovePlatformVariable(StringDictionary environmentVariables)
{
// EnvironmentVariables is case sensitive? Seriously?
var platformEnvironmentVariables = new List<string>();
foreach (string env in environmentVariables.Keys)
{
if (env.ToUpper() == "PLATFORM")
platformEnvironmentVariables.Add(env);
}
foreach (string env in platformEnvironmentVariables)
environmentVariables.Remove(env);
}
private static bool IsDebugMsBuildRequested()
{
return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1";
}
}
}

View file

@ -0,0 +1,210 @@
using System;
using System.Collections.Generic;
using System.IO;
using Godot;
using GodotTools.Internals;
using Directory = System.IO.Directory;
using Environment = System.Environment;
using File = System.IO.File;
using Path = System.IO.Path;
using OS = GodotTools.Utils.OS;
namespace GodotTools.Build
{
public static class MsBuildFinder
{
private static string _msbuildToolsPath = string.Empty;
private static string _msbuildUnixPath = string.Empty;
private static string _xbuildUnixPath = string.Empty;
public static string FindMsBuild()
{
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
if (OS.IsWindows())
{
switch (buildTool)
{
case GodotSharpBuilds.BuildTool.MsBuildVs:
{
if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_msbuildToolsPath = FindMsBuildToolsPathOnWindows();
if (_msbuildToolsPath.Empty())
{
throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}");
}
}
if (!_msbuildToolsPath.EndsWith("\\"))
_msbuildToolsPath += "\\";
return Path.Combine(_msbuildToolsPath, "MSBuild.exe");
}
case GodotSharpBuilds.BuildTool.MsBuildMono:
{
string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
if (!File.Exists(msbuildPath))
{
throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}");
}
return msbuildPath;
}
case GodotSharpBuilds.BuildTool.XBuild:
{
string xbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "xbuild.bat");
if (!File.Exists(xbuildPath))
{
throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameXbuild}'. Tried with path: {xbuildPath}");
}
return xbuildPath;
}
default:
throw new IndexOutOfRangeException("Invalid build tool in editor settings");
}
}
if (OS.IsUnix())
{
if (buildTool == GodotSharpBuilds.BuildTool.XBuild)
{
if (_xbuildUnixPath.Empty() || !File.Exists(_xbuildUnixPath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_xbuildUnixPath = FindBuildEngineOnUnix("msbuild");
}
if (_xbuildUnixPath.Empty())
{
throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameXbuild}'");
}
}
else
{
if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_msbuildUnixPath = FindBuildEngineOnUnix("msbuild");
}
if (_msbuildUnixPath.Empty())
{
throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'");
}
}
return buildTool != GodotSharpBuilds.BuildTool.XBuild ? _msbuildUnixPath : _xbuildUnixPath;
}
throw new PlatformNotSupportedException();
}
private static IEnumerable<string> MsBuildHintDirs
{
get
{
var result = new List<string>();
if (OS.IsOSX())
{
result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/");
result.Add("/usr/local/var/homebrew/linked/mono/bin/");
}
result.Add("/opt/novell/mono/bin/");
return result;
}
}
private static string FindBuildEngineOnUnix(string name)
{
string ret = OS.PathWhich(name);
if (!ret.Empty())
return ret;
string retFallback = OS.PathWhich($"{name}.exe");
if (!retFallback.Empty())
return retFallback;
foreach (string hintDir in MsBuildHintDirs)
{
string hintPath = Path.Combine(hintDir, name);
if (File.Exists(hintPath))
return hintPath;
}
return string.Empty;
}
private static string FindMsBuildToolsPathOnWindows()
{
if (!OS.IsWindows())
throw new PlatformNotSupportedException();
// Try to find 15.0 with vswhere
string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)");
vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"};
var outputArray = new Godot.Collections.Array<string>();
int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs,
blocking: true, output: (Godot.Collections.Array) outputArray);
if (exitCode == 0)
return string.Empty;
if (outputArray.Count == 0)
return string.Empty;
var lines = outputArray[1].Split('\n');
foreach (string line in lines)
{
int sepIdx = line.IndexOf(':');
if (sepIdx <= 0)
continue;
string key = line.Substring(0, sepIdx); // No need to trim
if (key != "installationPath")
continue;
string value = line.Substring(sepIdx + 1).StripEdges();
if (value.Empty())
throw new FormatException("installationPath value is empty");
if (!value.EndsWith("\\"))
value += "\\";
// Since VS2019, the directory is simply named "Current"
string msbuildDir = Path.Combine(value, "MSBuild\\Current\\Bin");
if (Directory.Exists(msbuildDir))
return msbuildDir;
// Directory name "15.0" is used in VS 2017
return Path.Combine(value, "MSBuild\\15.0\\Bin");
}
return string.Empty;
}
}
}

View file

@ -0,0 +1,115 @@
using Godot;
using System;
using System.Collections.Generic;
using Godot.Collections;
using GodotTools.Internals;
using GodotTools.ProjectEditor;
using File = GodotTools.Utils.File;
using Directory = GodotTools.Utils.Directory;
namespace GodotTools
{
public static class CSharpProject
{
public static string GenerateGameProject(string dir, string name, IEnumerable<string> files = null)
{
try
{
return ProjectGenerator.GenGameProject(dir, name, files);
}
catch (Exception e)
{
GD.PushError(e.ToString());
return string.Empty;
}
}
public static void AddItem(string projectPath, string itemType, string include)
{
if (!(bool) Internal.GlobalDef("mono/project/auto_update_project", true))
return;
ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include);
}
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static ulong ConvertToTimestamp(this DateTime value)
{
TimeSpan elapsedTime = value - Epoch;
return (ulong) elapsedTime.TotalSeconds;
}
public static void GenerateScriptsMetadata(string projectPath, string outputPath)
{
if (File.Exists(outputPath))
File.Delete(outputPath);
var oldDict = Internal.GetScriptsMetadataOrNothing();
var newDict = new Godot.Collections.Dictionary<string, object>();
foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile"))
{
string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath();
ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp();
if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar))
{
var oldFileDict = (Dictionary) oldFileVar;
if (ulong.TryParse((string) oldFileDict["modified_time"], out ulong storedModifiedTime))
{
if (storedModifiedTime == modifiedTime)
{
// No changes so no need to parse again
newDict[projectIncludeFile] = oldFileDict;
continue;
}
}
}
ScriptClassParser.ParseFileOrThrow(projectIncludeFile, out var classes);
string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile);
var classDict = new Dictionary();
foreach (var classDecl in classes)
{
if (classDecl.BaseCount == 0)
continue; // Does not inherit nor implement anything, so it can't be a script class
string classCmp = classDecl.Nested ?
classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
classDecl.Name;
if (classCmp != searchName)
continue;
classDict["namespace"] = classDecl.Namespace;
classDict["class_name"] = classDecl.Name;
classDict["nested"] = classDecl.Nested;
break;
}
if (classDict.Count == 0)
continue; // Not found
newDict[projectIncludeFile] = new Dictionary {["modified_time"] = $"{modifiedTime}", ["class"] = classDict};
}
if (newDict.Count > 0)
{
string json = JSON.Print(newDict);
string baseDir = outputPath.GetBaseDir();
if (!Directory.Exists(baseDir))
Directory.CreateDirectory(baseDir);
File.WriteAllText(outputPath, json);
}
}
}
}

View file

@ -0,0 +1,396 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using GodotTools.Build;
using GodotTools.Internals;
using GodotTools.Utils;
using Error = Godot.Error;
using File = GodotTools.Utils.File;
using Directory = GodotTools.Utils.Directory;
namespace GodotTools
{
public static class GodotSharpBuilds
{
private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>();
public const string PropNameMsbuildMono = "MSBuild (Mono)";
public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)";
public const string PropNameXbuild = "xbuild (Deprecated)";
public const string MsBuildIssuesFileName = "msbuild_issues.csv";
public const string MsBuildLogFileName = "msbuild_log.txt";
public enum BuildTool
{
MsBuildMono,
MsBuildVs,
XBuild // Deprecated
}
private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo)
{
var issuesFile = GetIssuesFilePath(buildInfo);
if (!File.Exists(issuesFile))
return;
File.Delete(issuesFile);
}
private static string _ApiFolderName(ApiAssemblyType apiType)
{
ulong apiHash = apiType == ApiAssemblyType.Core ?
Internal.GetCoreApiHash() :
Internal.GetEditorApiHash();
return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}";
}
private static void ShowBuildErrorDialog(string message)
{
GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error");
GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab();
}
public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException();
public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException();
private static string GetLogFilePath(MonoBuildInfo buildInfo)
{
return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName);
}
private static string GetIssuesFilePath(MonoBuildInfo buildInfo)
{
return Path.Combine(Godot.ProjectSettings.LocalizePath(buildInfo.LogsDirPath), MsBuildIssuesFileName);
}
private static void PrintVerbose(string text)
{
if (Godot.OS.IsStdoutVerbose())
Godot.GD.Print(text);
}
public static bool Build(MonoBuildInfo buildInfo)
{
if (BuildsInProgress.Contains(buildInfo))
throw new InvalidOperationException("A build is already in progress");
BuildsInProgress.Add(buildInfo);
try
{
MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo);
buildTab.OnBuildStart();
// Required in order to update the build tasks list
Internal.GodotMainIteration();
try
{
RemoveOldIssuesFile(buildInfo);
}
catch (IOException e)
{
buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
Console.Error.WriteLine(e);
}
try
{
int exitCode = BuildSystem.Build(buildInfo);
if (exitCode != 0)
PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error);
return exitCode == 0;
}
catch (Exception e)
{
buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
Console.Error.WriteLine(e);
return false;
}
}
finally
{
BuildsInProgress.Remove(buildInfo);
}
}
public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo)
{
if (BuildsInProgress.Contains(buildInfo))
throw new InvalidOperationException("A build is already in progress");
BuildsInProgress.Add(buildInfo);
try
{
MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo);
try
{
RemoveOldIssuesFile(buildInfo);
}
catch (IOException e)
{
buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
Console.Error.WriteLine(e);
}
try
{
int exitCode = await BuildSystem.BuildAsync(buildInfo);
if (exitCode != 0)
PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error);
return exitCode == 0;
}
catch (Exception e)
{
buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
Console.Error.WriteLine(e);
return false;
}
}
finally
{
BuildsInProgress.Remove(buildInfo);
}
}
public static bool BuildApiSolution(string apiSlnDir, string config)
{
string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln");
string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config);
string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll");
string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config);
string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll");
if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile))
return true; // The assemblies are in the output folder; assume the solution is already built
var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config);
// TODO Replace this global NoWarn with '#pragma warning' directives on generated files,
// once we start to actively document manually maintained C# classes
apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings
if (Build(apiBuildInfo))
return true;
ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution.");
return false;
}
private static bool CopyApiAssembly(string srcDir, string dstDir, string assemblyName, ApiAssemblyType apiType)
{
// Create destination directory if needed
if (!Directory.Exists(dstDir))
{
try
{
Directory.CreateDirectory(dstDir);
}
catch (IOException e)
{
ShowBuildErrorDialog($"Failed to create destination directory for the API assemblies. Exception message: {e.Message}");
return false;
}
}
string assemblyFile = assemblyName + ".dll";
string assemblySrc = Path.Combine(srcDir, assemblyFile);
string assemblyDst = Path.Combine(dstDir, assemblyFile);
if (!File.Exists(assemblyDst) || File.GetLastWriteTime(assemblySrc) > File.GetLastWriteTime(assemblyDst) ||
Internal.MetadataIsApiAssemblyInvalidated(apiType))
{
string xmlFile = $"{assemblyName}.xml";
string pdbFile = $"{assemblyName}.pdb";
try
{
File.Copy(Path.Combine(srcDir, xmlFile), Path.Combine(dstDir, xmlFile));
}
catch (IOException e)
{
Godot.GD.PushWarning(e.ToString());
}
try
{
File.Copy(Path.Combine(srcDir, pdbFile), Path.Combine(dstDir, pdbFile));
}
catch (IOException e)
{
Godot.GD.PushWarning(e.ToString());
}
try
{
File.Copy(assemblySrc, assemblyDst);
}
catch (IOException e)
{
ShowBuildErrorDialog($"Failed to copy {assemblyFile}. Exception message: {e.Message}");
return false;
}
Internal.MetadataSetApiAssemblyInvalidated(apiType, false);
}
return true;
}
public static bool MakeApiAssembly(ApiAssemblyType apiType, string config)
{
string apiName = apiType == ApiAssemblyType.Core ? ApiAssemblyNames.Core : ApiAssemblyNames.Editor;
string editorPrebuiltApiDir = Path.Combine(GodotSharpDirs.DataEditorPrebuiltApiDir, config);
string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, config);
if (File.Exists(Path.Combine(editorPrebuiltApiDir, $"{apiName}.dll")))
{
using (var copyProgress = new EditorProgress("mono_copy_prebuilt_api_assembly", $"Copying prebuilt {apiName} assembly...", 1))
{
copyProgress.Step($"Copying {apiName} assembly", 0);
return CopyApiAssembly(editorPrebuiltApiDir, resAssembliesDir, apiName, apiType);
}
}
const string apiSolutionName = ApiAssemblyNames.SolutionName;
using (var pr = new EditorProgress($"mono_build_release_{apiSolutionName}", $"Building {apiSolutionName} solution...", 3))
{
pr.Step($"Generating {apiSolutionName} solution", 0);
string apiSlnDir = Path.Combine(GodotSharpDirs.MonoSolutionsDir, _ApiFolderName(ApiAssemblyType.Core));
string apiSlnFile = Path.Combine(apiSlnDir, $"{apiSolutionName}.sln");
if (!Directory.Exists(apiSlnDir) || !File.Exists(apiSlnFile))
{
var bindingsGenerator = new BindingsGenerator();
if (!Godot.OS.IsStdoutVerbose())
bindingsGenerator.LogPrintEnabled = false;
Error err = bindingsGenerator.GenerateCsApi(apiSlnDir);
if (err != Error.Ok)
{
ShowBuildErrorDialog($"Failed to generate {apiSolutionName} solution. Error: {err}");
return false;
}
}
pr.Step($"Building {apiSolutionName} solution", 1);
if (!BuildApiSolution(apiSlnDir, config))
return false;
pr.Step($"Copying {apiName} assembly", 2);
// Copy the built assembly to the assemblies directory
string apiAssemblyDir = Path.Combine(apiSlnDir, apiName, "bin", config);
if (!CopyApiAssembly(apiAssemblyDir, resAssembliesDir, apiName, apiType))
return false;
}
return true;
}
public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
string apiConfig = config == "Release" ? "Release" : "Debug";
if (!MakeApiAssembly(ApiAssemblyType.Core, apiConfig))
return false;
if (!MakeApiAssembly(ApiAssemblyType.Editor, apiConfig))
return false;
using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
{
pr.Step("Building project solution", 0);
var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config);
// Add Godot defines
string constants = OS.IsWindows() ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
foreach (var godotDefine in godotDefines)
constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
if (Internal.GodotIsRealTDouble())
constants += "GODOT_REAL_T_IS_DOUBLE;";
constants += OS.IsWindows() ? "\"" : "\\\"";
buildInfo.CustomProperties.Add(constants);
if (!Build(buildInfo))
{
ShowBuildErrorDialog("Failed to build project solution");
return false;
}
}
return true;
}
public static bool EditorBuildCallback()
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
if (File.Exists(editorScriptsMetadataPath))
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
var godotDefines = new[]
{
Godot.OS.GetName(),
Internal.GodotIs32Bits() ? "32" : "64"
};
return BuildProjectBlocking("Tools", godotDefines);
}
public static void Initialize()
{
// Build tool settings
Internal.EditorDef("mono/builds/build_tool", OS.IsWindows() ? BuildTool.MsBuildVs : BuildTool.MsBuildMono);
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
["type"] = Godot.Variant.Type.Int,
["name"] = "mono/builds/build_tool",
["hint"] = Godot.PropertyHint.Enum,
["hint_string"] = OS.IsWindows() ?
$"{PropNameMsbuildMono},{PropNameMsbuildVs},{PropNameXbuild}" :
$"{PropNameMsbuildMono},{PropNameXbuild}"
});
Internal.EditorDef("mono/builds/print_build_output", false);
}
}
}

View file

@ -0,0 +1,538 @@
using Godot;
using GodotTools.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using GodotTools.Internals;
using GodotTools.ProjectEditor;
using File = GodotTools.Utils.File;
using Path = System.IO.Path;
using OS = GodotTools.Utils.OS;
namespace GodotTools
{
public class GodotSharpEditor : EditorPlugin, ISerializationListener
{
private EditorSettings editorSettings;
private PopupMenu menuPopup;
private AcceptDialog errorDialog;
private AcceptDialog aboutDialog;
private CheckBox aboutDialogCheckBox;
private ToolButton bottomPanelBtn;
private MonoDevelopInstance monoDevelopInstance;
private MonoDevelopInstance visualStudioForMacInstance;
public MonoBottomPanel MonoBottomPanel { get; private set; }
private bool CreateProjectSolution()
{
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...", 2)) // TTR("Generating solution...")
{
pr.Step("Generating C# project..."); // TTR("Generating C# project...")
string resourceDir = ProjectSettings.GlobalizePath("res://");
string path = resourceDir;
string name = (string) ProjectSettings.GetSetting("application/config/name");
if (name.Empty())
name = "UnnamedProject";
string guid = CSharpProject.GenerateGameProject(path, name);
if (guid.Length > 0)
{
var solution = new DotNetSolution(name)
{
DirectoryPath = path
};
var projectInfo = new DotNetSolution.ProjectInfo
{
Guid = guid,
PathRelativeToSolution = name + ".csproj",
Configs = new List<string> {"Debug", "Release", "Tools"}
};
solution.AddNewProject(name, projectInfo);
try
{
solution.Save();
}
catch (IOException e)
{
ShowErrorDialog($"Failed to save solution. Exception message: {e.Message}"); // TTR
return false;
}
string apiConfig = "Debug";
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, apiConfig))
return false;
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, apiConfig))
return false;
pr.Step("Done"); // TTR("Done")
// Here, after all calls to progress_task_step
CallDeferred(nameof(_RemoveCreateSlnMenuOption));
}
else
{
ShowErrorDialog("Failed to create C# project."); // TTR
}
return true;
}
}
private static int _makeApiSolutionsAttempts = 100;
private static bool _makeApiSolutionsRecursionGuard = false;
private void _MakeApiSolutionsIfNeeded()
{
// I'm sick entirely of ProgressDialog
if (Internal.IsMessageQueueFlushing() || Engine.GetMainLoop() == null)
{
if (_makeApiSolutionsAttempts == 0) // This better never happen or I swear...
throw new TimeoutException();
if (Engine.GetMainLoop() != null)
{
if (!Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)))
Engine.GetMainLoop().Connect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded));
}
else
{
CallDeferred(nameof(_MakeApiSolutionsIfNeededImpl));
}
_makeApiSolutionsAttempts--;
return;
}
// Recursion guard needed because signals don't play well with ProgressDialog either, but unlike
// the message queue, with signals the collateral damage should be minimal in the worst case.
if (!_makeApiSolutionsRecursionGuard)
{
_makeApiSolutionsRecursionGuard = true;
// Oneshot signals don't play well with ProgressDialog either, so we do it this way instead
if (Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)))
Engine.GetMainLoop().Disconnect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded));
_MakeApiSolutionsIfNeededImpl();
_makeApiSolutionsRecursionGuard = false;
}
}
private void _MakeApiSolutionsIfNeededImpl()
{
// If the project has a solution and C# project make sure the API assemblies are present and up to date
string api_config = "Debug";
string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, api_config);
if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Core}.dll")) ||
Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Core))
{
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, api_config))
return;
}
if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Editor}.dll")) ||
Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Editor))
{
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, api_config))
return; // Redundant? I don't think so!
}
}
private void _RemoveCreateSlnMenuOption()
{
menuPopup.RemoveItem(menuPopup.GetItemIndex((int) MenuOptions.CreateSln));
bottomPanelBtn.Show();
}
private void _ShowAboutDialog()
{
bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start");
aboutDialogCheckBox.Pressed = showOnStart;
aboutDialog.PopupCenteredMinsize();
}
private void _ToggleAboutDialogOnStart(bool enabled)
{
bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start");
if (showOnStart != enabled)
editorSettings.SetSetting("mono/editor/show_info_on_start", enabled);
}
private void _MenuOptionPressed(MenuOptions id)
{
switch (id)
{
case MenuOptions.CreateSln:
CreateProjectSolution();
break;
case MenuOptions.AboutCSharp:
_ShowAboutDialog();
break;
default:
throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option");
}
}
private void _BuildSolutionPressed()
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
{
if (!CreateProjectSolution())
return; // Failed to create solution
}
Instance.MonoBottomPanel.BuildProjectPressed();
}
public override void _Notification(int what)
{
base._Notification(what);
if (what == NotificationReady)
{
bool showInfoDialog = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start");
if (showInfoDialog)
{
aboutDialog.PopupExclusive = true;
_ShowAboutDialog();
// Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on.
aboutDialog.PopupExclusive = false;
}
}
}
public enum MenuOptions
{
CreateSln,
AboutCSharp,
}
public enum ExternalEditor
{
None,
VisualStudio, // TODO (Windows-only)
VisualStudioForMac, // Mac-only
MonoDevelop,
VsCode
}
public void ShowErrorDialog(string message, string title = "Error")
{
errorDialog.WindowTitle = title;
errorDialog.DialogText = message;
errorDialog.PopupCenteredMinsize();
}
private static string _vsCodePath = string.Empty;
private static readonly string[] VsCodeNames =
{
"code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss"
};
public Error OpenInExternalEditor(Script script, int line, int col)
{
var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor");
switch (editor)
{
case ExternalEditor.VsCode:
{
if (_vsCodePath.Empty() || !File.Exists(_vsCodePath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_vsCodePath = VsCodeNames.SelectFirstNotNull(OS.PathWhich, orElse: string.Empty);
}
var args = new List<string>();
bool osxAppBundleInstalled = false;
if (OS.IsOSX())
{
// The package path is '/Applications/Visual Studio Code.app'
const string vscodeBundleId = "com.microsoft.VSCode";
osxAppBundleInstalled = Internal.IsOsxAppBundleInstalled(vscodeBundleId);
if (osxAppBundleInstalled)
{
args.Add("-b");
args.Add(vscodeBundleId);
// The reusing of existing windows made by the 'open' command might not choose a wubdiw that is
// editing our folder. It's better to ask for a new window and let VSCode do the window management.
args.Add("-n");
// The open process must wait until the application finishes (which is instant in VSCode's case)
args.Add("--wait-apps");
args.Add("--args");
}
}
var resourcePath = ProjectSettings.GlobalizePath("res://");
args.Add(resourcePath);
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
if (line >= 0)
{
args.Add("-g");
args.Add($"{scriptPath}:{line + 1}:{col}");
}
else
{
args.Add(scriptPath);
}
string command;
if (OS.IsOSX())
{
if (!osxAppBundleInstalled && _vsCodePath.Empty())
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
}
command = osxAppBundleInstalled ? "/usr/bin/open" : _vsCodePath;
}
else
{
if (_vsCodePath.Empty())
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
}
command = _vsCodePath;
}
try
{
OS.RunProcess(command, args);
}
catch (Exception e)
{
GD.PushError($"Error when trying to run code editor: VSCode. Exception message: '{e.Message}'");
}
break;
}
case ExternalEditor.VisualStudioForMac:
goto case ExternalEditor.MonoDevelop;
case ExternalEditor.MonoDevelop:
{
MonoDevelopInstance GetMonoDevelopInstance(string solutionPath)
{
if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac)
{
if (visualStudioForMacInstance == null)
visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac);
return visualStudioForMacInstance;
}
if (monoDevelopInstance == null)
monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop);
return monoDevelopInstance;
}
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
if (line >= 0)
scriptPath += $";{line + 1};{col}";
GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath);
break;
}
case ExternalEditor.None:
return Error.Unavailable;
default:
throw new ArgumentOutOfRangeException();
}
return Error.Ok;
}
public bool OverridesExternalEditor()
{
return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None;
}
public override bool Build()
{
return GodotSharpBuilds.EditorBuildCallback();
}
public override void EnablePlugin()
{
base.EnablePlugin();
if (Instance != null)
throw new InvalidOperationException();
Instance = this;
var editorInterface = GetEditorInterface();
var editorBaseControl = editorInterface.GetBaseControl();
editorSettings = editorInterface.GetEditorSettings();
errorDialog = new AcceptDialog();
editorBaseControl.AddChild(errorDialog);
MonoBottomPanel = new MonoBottomPanel();
bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono"); // TTR("Mono")
AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"});
menuPopup = new PopupMenu();
menuPopup.Hide();
menuPopup.SetAsToplevel(true);
AddToolSubmenuItem("Mono", menuPopup);
// TODO: Remove or edit this info dialog once Mono support is no longer in alpha
{
menuPopup.AddItem("About C# support", (int) MenuOptions.AboutCSharp); // TTR("About C# support")
aboutDialog = new AcceptDialog();
editorBaseControl.AddChild(aboutDialog);
aboutDialog.WindowTitle = "Important: C# support is not feature-complete";
// We don't use DialogText as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox
// we'll add. Instead we add containers and a new autowrapped Label inside.
// Main VBoxContainer (icon + label on top, checkbox at bottom)
var aboutVBox = new VBoxContainer();
aboutDialog.AddChild(aboutVBox);
// HBoxContainer for icon + label
var aboutHBox = new HBoxContainer();
aboutVBox.AddChild(aboutHBox);
var aboutIcon = new TextureRect();
aboutIcon.Texture = aboutIcon.GetIcon("NodeWarning", "EditorIcons");
aboutHBox.AddChild(aboutIcon);
var aboutLabel = new Label();
aboutHBox.AddChild(aboutLabel);
aboutLabel.RectMinSize = new Vector2(600, 150) * Internal.EditorScale;
aboutLabel.SizeFlagsVertical = (int) Control.SizeFlags.ExpandFill;
aboutLabel.Autowrap = true;
aboutLabel.Text =
"C# support in Godot Engine is in late alpha stage and, while already usable, " +
"it is not meant for use in production.\n\n" +
"Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " +
"Bugs and usability issues will be addressed gradually over future releases, " +
"potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" +
"If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" +
" https://github.com/godotengine/godot/issues\n\n" +
"Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!";
Internal.EditorDef("mono/editor/show_info_on_start", true);
// CheckBox in main container
aboutDialogCheckBox = new CheckBox {Text = "Show this warning when starting the editor"};
aboutDialogCheckBox.Connect("toggled", this, nameof(_ToggleAboutDialogOnStart));
aboutVBox.AddChild(aboutDialogCheckBox);
}
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
// Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized.
CallDeferred(nameof(_MakeApiSolutionsIfNeeded));
}
else
{
bottomPanelBtn.Hide();
menuPopup.AddItem("Create C# solution", (int) MenuOptions.CreateSln); // TTR("Create C# solution")
}
menuPopup.Connect("id_pressed", this, nameof(_MenuOptionPressed));
var buildButton = new ToolButton
{
Text = "Build",
HintTooltip = "Build solution",
FocusMode = Control.FocusModeEnum.None
};
buildButton.Connect("pressed", this, nameof(_BuildSolutionPressed));
AddControlToContainer(CustomControlContainer.Toolbar, buildButton);
// External editor settings
Internal.EditorDef("mono/editor/external_editor", ExternalEditor.None);
string settingsHintStr = "Disabled";
if (OS.IsWindows())
{
settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
$",Visual Studio Code:{(int) ExternalEditor.VsCode}";
}
else if (OS.IsOSX())
{
settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" +
$",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
$",Visual Studio Code:{(int) ExternalEditor.VsCode}";
}
else if (OS.IsUnix())
{
settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
$",Visual Studio Code:{(int) ExternalEditor.VsCode}";
}
editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
["type"] = Variant.Type.Int,
["name"] = "mono/editor/external_editor",
["hint"] = PropertyHint.Enum,
["hint_string"] = settingsHintStr
});
// Export plugin
AddExportPlugin(new GodotSharpExport());
GodotSharpBuilds.Initialize();
}
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
Instance = this;
}
// Singleton
public static GodotSharpEditor Instance { get; private set; }
private GodotSharpEditor()
{
}
}
}

View file

@ -0,0 +1,197 @@
using Godot;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using GodotTools.Core;
using GodotTools.Internals;
using Directory = GodotTools.Utils.Directory;
using File = GodotTools.Utils.File;
using Path = System.IO.Path;
namespace GodotTools
{
public class GodotSharpExport : EditorExportPlugin
{
private void AddFile(string srcPath, string dstPath, bool remap = false)
{
AddFile(dstPath, File.ReadAllBytes(srcPath), remap);
}
public override void _ExportFile(string path, string type, string[] features)
{
base._ExportFile(path, type, features);
if (type != Internal.CSharpLanguageType)
return;
if (Path.GetExtension(path) != $".{Internal.CSharpLanguageExtension}")
throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path));
// TODO What if the source file is not part of the game's C# project
bool includeScriptsContent = (bool) ProjectSettings.GetSetting("mono/export/include_scripts_content");
if (!includeScriptsContent)
{
// We don't want to include the source code on exported games
AddFile(path, new byte[] { }, remap: false);
Skip();
}
}
public override void _ExportBegin(string[] features, bool isDebug, string path, int flags)
{
base._ExportBegin(features, isDebug, path, flags);
try
{
_ExportBeginImpl(features, isDebug, path, flags);
}
catch (Exception e)
{
GD.PushError($"Failed to export project. Exception message: {e.Message}");
Console.Error.WriteLine(e);
}
}
public void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags)
{
// TODO Right now there is no way to stop the export process with an error
if (File.Exists(GodotSharpDirs.ProjectSlnPath))
{
string buildConfig = isDebug ? "Debug" : "Release";
string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}");
CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
AddFile(scriptsMetadataPath, scriptsMetadataPath);
// Turn export features into defines
var godotDefines = features;
if (!GodotSharpBuilds.BuildProjectBlocking(buildConfig, godotDefines))
{
GD.PushError("Failed to build project");
return;
}
// Add dependency assemblies
var dependencies = new Godot.Collections.Dictionary<string, string>();
var projectDllName = (string) ProjectSettings.GetSetting("application/config/name");
if (projectDllName.Empty())
{
projectDllName = "UnnamedProject";
}
string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");
dependencies[projectDllName] = projectDllSrcPath;
{
string templatesDir = Internal.FullTemplatesDir;
string androidBclDir = Path.Combine(templatesDir, "android-bcl");
string customLibDir = features.Contains("Android") && Directory.Exists(androidBclDir) ? androidBclDir : string.Empty;
GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies);
}
string apiConfig = isDebug ? "Debug" : "Release";
string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig);
foreach (var dependency in dependencies)
{
string dependSrcPath = dependency.Value;
string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile());
AddFile(dependSrcPath, dependDstPath);
}
}
// Mono specific export template extras (data dir)
ExportDataDirectory(features, isDebug, path);
}
private static void ExportDataDirectory(IEnumerable<string> features, bool debug, string path)
{
var featureSet = new HashSet<string>(features);
if (!PlatformHasTemplateDir(featureSet))
return;
string templateDirName = "data.mono";
if (featureSet.Contains("Windows"))
{
templateDirName += ".windows";
templateDirName += featureSet.Contains("64") ? ".64" : ".32";
}
else if (featureSet.Contains("X11"))
{
templateDirName += ".x11";
templateDirName += featureSet.Contains("64") ? ".64" : ".32";
}
else
{
throw new NotSupportedException("Target platform not supported");
}
templateDirName += debug ? ".release_debug" : ".release";
string templateDirPath = Path.Combine(Internal.FullTemplatesDir, templateDirName);
if (!Directory.Exists(templateDirPath))
throw new FileNotFoundException("Data template directory not found");
string outputDir = new FileInfo(path).Directory?.FullName ??
throw new FileNotFoundException("Base directory not found");
string outputDataDir = Path.Combine(outputDir, DataDirName);
if (Directory.Exists(outputDataDir))
Directory.Delete(outputDataDir, recursive: true); // Clean first
Directory.CreateDirectory(outputDataDir);
foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1)));
}
foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories))
{
File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1)));
}
}
private static bool PlatformHasTemplateDir(IEnumerable<string> featureSet)
{
// OSX export templates are contained in a zip, so we place
// our custom template inside it and let Godot do the rest.
return !featureSet.Any(f => new[] {"OSX", "Android"}.Contains(f));
}
private static string DataDirName
{
get
{
var appName = (string) ProjectSettings.GetSetting("application/config/name");
string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false);
return $"data_{appNameSafe}";
}
}
private static void GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath,
string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies) =>
internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath,
string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies);
}
}

View file

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>GodotTools</RootNamespace>
<AssemblyName>GodotTools</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
<GodotApiConfiguration>Debug</GodotApiConfiguration>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="GodotSharp">
<HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath>
</Reference>
<Reference Include="GodotSharpEditor">
<HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharpEditor.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Build\MsBuildFinder.cs" />
<Compile Include="Internals\BindingsGenerator.cs" />
<Compile Include="Internals\EditorProgress.cs" />
<Compile Include="Internals\GodotSharpDirs.cs" />
<Compile Include="Internals\Internal.cs" />
<Compile Include="Internals\ScriptClassParser.cs" />
<Compile Include="MonoDevelopInstance.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Build\BuildSystem.cs" />
<Compile Include="Utils\Directory.cs" />
<Compile Include="Utils\File.cs" />
<Compile Include="Utils\OS.cs" />
<Compile Include="GodotSharpEditor.cs" />
<Compile Include="GodotSharpBuilds.cs" />
<Compile Include="HotReloadAssemblyWatcher.cs" />
<Compile Include="MonoBuildInfo.cs" />
<Compile Include="MonoBuildTab.cs" />
<Compile Include="MonoBottomPanel.cs" />
<Compile Include="GodotSharpExport.cs" />
<Compile Include="CSharpProject.cs" />
<Compile Include="Utils\CollectionExtensions.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj">
<Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project>
<Name>GodotTools.BuildLogger</Name>
</ProjectReference>
<ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj">
<Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project>
<Name>GodotTools.ProjectEditor</Name>
</ProjectReference>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
<Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
<Name>GodotTools.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Editor" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,47 @@
using Godot;
using GodotTools.Internals;
namespace GodotTools
{
public class HotReloadAssemblyWatcher : Node
{
private Timer watchTimer;
public override void _Notification(int what)
{
if (what == MainLoop.NotificationWmFocusIn)
{
RestartTimer();
if (Internal.IsAssembliesReloadingNeeded())
Internal.ReloadAssemblies(softReload: false);
}
}
private void TimerTimeout()
{
if (Internal.IsAssembliesReloadingNeeded())
Internal.ReloadAssemblies(softReload: false);
}
public void RestartTimer()
{
watchTimer.Stop();
watchTimer.Start();
}
public override void _Ready()
{
base._Ready();
watchTimer = new Timer
{
OneShot = false,
WaitTime = (float) Internal.EditorDef("mono/assembly_watch_interval_sec", 0.5)
};
watchTimer.Connect("timeout", this, nameof(TimerTimeout));
AddChild(watchTimer);
watchTimer.Start();
}
}
}

View file

@ -0,0 +1,87 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace GodotTools.Internals
{
public class BindingsGenerator : IDisposable
{
class BindingsGeneratorSafeHandle : SafeHandle
{
public BindingsGeneratorSafeHandle(IntPtr handle) : base(IntPtr.Zero, true)
{
this.handle = handle;
}
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
internal_Dtor(handle);
return true;
}
}
private BindingsGeneratorSafeHandle safeHandle;
private bool disposed = false;
public bool LogPrintEnabled
{
get => internal_LogPrintEnabled(GetPtr());
set => internal_SetLogPrintEnabled(GetPtr(), value);
}
public static uint Version => internal_Version();
public static uint CsGlueVersion => internal_CsGlueVersion();
public Godot.Error GenerateCsApi(string outputDir) => internal_GenerateCsApi(GetPtr(), outputDir);
internal IntPtr GetPtr()
{
if (disposed)
throw new ObjectDisposedException(GetType().FullName);
return safeHandle.DangerousGetHandle();
}
public void Dispose()
{
if (disposed)
return;
if (safeHandle != null && !safeHandle.IsInvalid)
{
safeHandle.Dispose();
safeHandle = null;
}
disposed = true;
}
public BindingsGenerator()
{
safeHandle = new BindingsGeneratorSafeHandle(internal_Ctor());
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern IntPtr internal_Ctor();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_Dtor(IntPtr handle);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_LogPrintEnabled(IntPtr handle);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_SetLogPrintEnabled(IntPtr handle, bool enabled);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern Godot.Error internal_GenerateCsApi(IntPtr handle, string outputDir);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern uint internal_Version();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern uint internal_CsGlueVersion();
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Runtime.CompilerServices;
using Godot;
namespace GodotTools.Internals
{
public class EditorProgress : IDisposable
{
public string Task { get; }
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_Create(string task, string label, int amount, bool canCancel);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_Dispose(string task);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_Step(string task, string state, int step, bool forceRefresh);
public EditorProgress(string task, string label, int amount, bool canCancel = false)
{
Task = task;
internal_Create(task, label, amount, canCancel);
}
~EditorProgress()
{
// Should never rely on the GC to dispose EditorProgress.
// It should be disposed immediately when the task finishes.
GD.PushError("EditorProgress disposed by the Garbage Collector");
Dispose();
}
public void Dispose()
{
internal_Dispose(Task);
GC.SuppressFinalize(this);
}
public void Step(string state, int step = -1, bool forceRefresh = true)
{
internal_Step(Task, state, step, forceRefresh);
}
public bool TryStep(string state, int step = -1, bool forceRefresh = true)
{
return internal_Step(Task, state, step, forceRefresh);
}
}
}

View file

@ -0,0 +1,91 @@
using System.Runtime.CompilerServices;
namespace GodotTools.Internals
{
public static class GodotSharpDirs
{
public static string ResDataDir => internal_ResDataDir();
public static string ResMetadataDir => internal_ResMetadataDir();
public static string ResAssembliesBaseDir => internal_ResAssembliesBaseDir();
public static string ResAssembliesDir => internal_ResAssembliesDir();
public static string ResConfigDir => internal_ResConfigDir();
public static string ResTempDir => internal_ResTempDir();
public static string ResTempAssembliesBaseDir => internal_ResTempAssembliesBaseDir();
public static string ResTempAssembliesDir => internal_ResTempAssembliesDir();
public static string MonoUserDir => internal_MonoUserDir();
public static string MonoLogsDir => internal_MonoLogsDir();
#region Tools-only
public static string MonoSolutionsDir => internal_MonoSolutionsDir();
public static string BuildLogsDirs => internal_BuildLogsDirs();
public static string ProjectSlnPath => internal_ProjectSlnPath();
public static string ProjectCsProjPath => internal_ProjectCsProjPath();
public static string DataEditorToolsDir => internal_DataEditorToolsDir();
public static string DataEditorPrebuiltApiDir => internal_DataEditorPrebuiltApiDir();
#endregion
public static string DataMonoEtcDir => internal_DataMonoEtcDir();
public static string DataMonoLibDir => internal_DataMonoLibDir();
#region Windows-only
public static string DataMonoBinDir => internal_DataMonoBinDir();
#endregion
#region Internal
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResDataDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResMetadataDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResAssembliesBaseDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResAssembliesDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResConfigDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResTempDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResTempAssembliesBaseDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResTempAssembliesDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoUserDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoLogsDir();
#region Tools-only
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoSolutionsDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_BuildLogsDirs();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ProjectSlnPath();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ProjectCsProjPath();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataEditorToolsDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataEditorPrebuiltApiDir();
#endregion
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataMonoEtcDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataMonoLibDir();
#region Windows-only
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataMonoBinDir();
#endregion
#endregion
}
}

View file

@ -0,0 +1,127 @@
using System;
using System.Runtime.CompilerServices;
using Godot;
using Godot.Collections;
namespace GodotTools.Internals
{
public static class Internal
{
public const string CSharpLanguageType = "CSharpScript";
public const string CSharpLanguageExtension = "cs";
public static float EditorScale => internal_EditorScale();
public static object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) =>
internal_GlobalDef(setting, defaultValue, restartIfChanged);
public static object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) =>
internal_EditorDef(setting, defaultValue, restartIfChanged);
public static string FullTemplatesDir =>
internal_FullTemplatesDir();
public static string SimplifyGodotPath(this string path) => internal_SimplifyGodotPath(path);
public static bool IsOsxAppBundleInstalled(string bundleId) => internal_IsOsxAppBundleInstalled(bundleId);
public static bool MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType) =>
internal_MetadataIsApiAssemblyInvalidated(apiType);
public static void MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated) =>
internal_MetadataSetApiAssemblyInvalidated(apiType, invalidated);
public static bool IsMessageQueueFlushing() => internal_IsMessageQueueFlushing();
public static bool GodotIs32Bits() => internal_GodotIs32Bits();
public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble();
public static void GodotMainIteration() => internal_GodotMainIteration();
public static ulong GetCoreApiHash() => internal_GetCoreApiHash();
public static ulong GetEditorApiHash() => internal_GetEditorApiHash();
public static bool IsAssembliesReloadingNeeded() => internal_IsAssembliesReloadingNeeded();
public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload);
public static void ScriptEditorDebuggerReloadScripts() => internal_ScriptEditorDebuggerReloadScripts();
public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) =>
internal_ScriptEditorEdit(resource, line, col, grabFocus);
public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen();
public static Dictionary<string, object> GetScriptsMetadataOrNothing() =>
internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>));
public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot();
// Internal Calls
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern float internal_EditorScale();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object internal_GlobalDef(string setting, object defaultValue, bool restartIfChanged);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object internal_EditorDef(string setting, object defaultValue, bool restartIfChanged);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_FullTemplatesDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_SimplifyGodotPath(this string path);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_IsOsxAppBundleInstalled(string bundleId);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_IsMessageQueueFlushing();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_GodotIs32Bits();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_GodotIsRealTDouble();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_GodotMainIteration();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern ulong internal_GetCoreApiHash();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern ulong internal_GetEditorApiHash();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_IsAssembliesReloadingNeeded();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_ReloadAssemblies(bool softReload);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_ScriptEditorDebuggerReloadScripts();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_EditorNodeShowScriptScreen();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoWindowsInstallRoot();
}
}

View file

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Godot;
using Godot.Collections;
namespace GodotTools.Internals
{
public static class ScriptClassParser
{
public class ClassDecl
{
public string Name { get; }
public string Namespace { get; }
public bool Nested { get; }
public int BaseCount { get; }
public ClassDecl(string name, string @namespace, bool nested, int baseCount)
{
Name = name;
Namespace = @namespace;
Nested = nested;
BaseCount = baseCount;
}
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes);
public static void ParseFileOrThrow(string filePath, out IEnumerable<ClassDecl> classes)
{
var classesArray = new Array<Dictionary>();
var error = internal_ParseFile(filePath, classesArray);
if (error != Error.Ok)
throw new Exception($"Failed to determine namespace and class for script: {filePath}. Parse error: {error}");
var classesList = new List<ClassDecl>();
foreach (var classDeclDict in classesArray)
{
classesList.Add(new ClassDecl(
(string) classDeclDict["name"],
(string) classDeclDict["namespace"],
(bool) classDeclDict["nested"],
(int) classDeclDict["base_count"]
));
}
classes = classesList;
}
}
}

View file

@ -0,0 +1,342 @@
using Godot;
using System;
using System.IO;
using Godot.Collections;
using GodotTools.Internals;
using File = GodotTools.Utils.File;
using Path = System.IO.Path;
namespace GodotTools
{
public class MonoBottomPanel : VBoxContainer
{
private EditorInterface editorInterface;
private TabContainer panelTabs;
private VBoxContainer panelBuildsTab;
private ItemList buildTabsList;
private TabContainer buildTabs;
private ToolButton warningsBtn;
private ToolButton errorsBtn;
private Button viewLogBtn;
private void _UpdateBuildTabsList()
{
buildTabsList.Clear();
int currentTab = buildTabs.CurrentTab;
bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount();
for (int i = 0; i < buildTabs.GetChildCount(); i++)
{
var tab = (MonoBuildTab) buildTabs.GetChild(i);
if (tab == null)
continue;
string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
itemName += " [" + tab.BuildInfo.Configuration + "]";
buildTabsList.AddItem(itemName, tab.IconTexture);
string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
itemTooltip += "\nStatus: ";
if (tab.BuildExited)
itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored";
else
itemTooltip += "Running";
if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error)
itemTooltip += $"\nErrors: {tab.ErrorCount}";
itemTooltip += $"\nWarnings: {tab.WarningCount}";
buildTabsList.SetItemTooltip(i, itemTooltip);
if (noCurrentTab || currentTab == i)
{
buildTabsList.Select(i);
_BuildTabsItemSelected(i);
}
}
}
public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo)
{
foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren()))
{
if (buildTab.BuildInfo.Equals(buildInfo))
return buildTab;
}
var newBuildTab = new MonoBuildTab(buildInfo);
AddBuildTab(newBuildTab);
return newBuildTab;
}
private void _BuildTabsItemSelected(int idx)
{
if (idx < 0 || idx >= buildTabs.GetTabCount())
throw new IndexOutOfRangeException();
buildTabs.CurrentTab = idx;
if (!buildTabs.Visible)
buildTabs.Visible = true;
warningsBtn.Visible = true;
errorsBtn.Visible = true;
viewLogBtn.Visible = true;
}
private void _BuildTabsNothingSelected()
{
if (buildTabs.GetTabCount() != 0)
{
// just in case
buildTabs.Visible = false;
// This callback is called when clicking on the empty space of the list.
// ItemList won't deselect the items automatically, so we must do it ourselves.
buildTabsList.UnselectAll();
}
warningsBtn.Visible = false;
errorsBtn.Visible = false;
viewLogBtn.Visible = false;
}
private void _WarningsToggled(bool pressed)
{
int currentTab = buildTabs.CurrentTab;
if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
throw new InvalidOperationException("No tab selected");
var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab);
buildTab.WarningsVisible = pressed;
buildTab.UpdateIssuesList();
}
private void _ErrorsToggled(bool pressed)
{
int currentTab = buildTabs.CurrentTab;
if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
throw new InvalidOperationException("No tab selected");
var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab);
buildTab.ErrorsVisible = pressed;
buildTab.UpdateIssuesList();
}
public void BuildProjectPressed()
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return; // No solution to build
string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
if (File.Exists(editorScriptsMetadataPath))
{
try
{
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
}
catch (IOException e)
{
GD.PushError($"Failed to copy scripts metadata file. Exception message: {e.Message}");
return;
}
}
var godotDefines = new[]
{
OS.GetName(),
Internal.GodotIs32Bits() ? "32" : "64"
};
bool buildSuccess = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines);
if (!buildSuccess)
return;
// Notify running game for hot-reload
Internal.ScriptEditorDebuggerReloadScripts();
// Hot-reload in the editor
GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
if (Internal.IsAssembliesReloadingNeeded())
Internal.ReloadAssemblies(softReload: false);
}
private void _ViewLogPressed()
{
if (!buildTabsList.IsAnythingSelected())
return;
var selectedItems = buildTabsList.GetSelectedItems();
if (selectedItems.Length != 1)
throw new InvalidOperationException($"Expected 1 selected item, got {selectedItems.Length}");
int selectedItem = selectedItems[0];
var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem);
OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName));
}
public override void _Notification(int what)
{
base._Notification(what);
if (what == EditorSettings.NotificationEditorSettingsChanged)
{
var editorBaseControl = editorInterface.GetBaseControl();
panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles"));
panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles"));
panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles"));
}
}
public void AddBuildTab(MonoBuildTab buildTab)
{
buildTabs.AddChild(buildTab);
RaiseBuildTab(buildTab);
}
public void RaiseBuildTab(MonoBuildTab buildTab)
{
if (buildTab.GetParent() != buildTabs)
throw new InvalidOperationException("Build tab is not in the tabs list");
buildTabs.MoveChild(buildTab, 0);
_UpdateBuildTabsList();
}
public void ShowBuildTab()
{
for (int i = 0; i < panelTabs.GetTabCount(); i++)
{
if (panelTabs.GetTabControl(i) == panelBuildsTab)
{
panelTabs.CurrentTab = i;
GodotSharpEditor.Instance.MakeBottomPanelItemVisible(this);
return;
}
}
GD.PushError("Builds tab not found");
}
public override void _Ready()
{
base._Ready();
editorInterface = GodotSharpEditor.Instance.GetEditorInterface();
var editorBaseControl = editorInterface.GetBaseControl();
SizeFlagsVertical = (int) SizeFlags.ExpandFill;
SetAnchorsAndMarginsPreset(LayoutPreset.Wide);
panelTabs = new TabContainer
{
TabAlign = TabContainer.TabAlignEnum.Left,
RectMinSize = new Vector2(0, 228) * Internal.EditorScale,
SizeFlagsVertical = (int) SizeFlags.ExpandFill
};
panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles"));
panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles"));
panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles"));
AddChild(panelTabs);
{
// Builds tab
panelBuildsTab = new VBoxContainer
{
Name = "Builds", // TTR
SizeFlagsHorizontal = (int) SizeFlags.ExpandFill
};
panelTabs.AddChild(panelBuildsTab);
var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill};
panelBuildsTab.AddChild(toolBarHBox);
var buildProjectBtn = new Button
{
Text = "Build Project", // TTR
FocusMode = FocusModeEnum.None
};
buildProjectBtn.Connect("pressed", this, nameof(BuildProjectPressed));
toolBarHBox.AddChild(buildProjectBtn);
toolBarHBox.AddSpacer(begin: false);
warningsBtn = new ToolButton
{
Text = "Warnings", // TTR
ToggleMode = true,
Pressed = true,
Visible = false,
FocusMode = FocusModeEnum.None
};
warningsBtn.Connect("toggled", this, nameof(_WarningsToggled));
toolBarHBox.AddChild(warningsBtn);
errorsBtn = new ToolButton
{
Text = "Errors", // TTR
ToggleMode = true,
Pressed = true,
Visible = false,
FocusMode = FocusModeEnum.None
};
errorsBtn.Connect("toggled", this, nameof(_ErrorsToggled));
toolBarHBox.AddChild(errorsBtn);
toolBarHBox.AddSpacer(begin: false);
viewLogBtn = new Button
{
Text = "View log", // TTR
FocusMode = FocusModeEnum.None,
Visible = false
};
viewLogBtn.Connect("pressed", this, nameof(_ViewLogPressed));
toolBarHBox.AddChild(viewLogBtn);
var hsc = new HSplitContainer
{
SizeFlagsHorizontal = (int) SizeFlags.ExpandFill,
SizeFlagsVertical = (int) SizeFlags.ExpandFill
};
panelBuildsTab.AddChild(hsc);
buildTabsList = new ItemList {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill};
buildTabsList.Connect("item_selected", this, nameof(_BuildTabsItemSelected));
buildTabsList.Connect("nothing_selected", this, nameof(_BuildTabsNothingSelected));
hsc.AddChild(buildTabsList);
buildTabs = new TabContainer
{
TabAlign = TabContainer.TabAlignEnum.Left,
SizeFlagsHorizontal = (int) SizeFlags.ExpandFill,
TabsVisible = false
};
hsc.AddChild(buildTabs);
}
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using Godot;
using Godot.Collections;
using GodotTools.Internals;
using Path = System.IO.Path;
namespace GodotTools
{
[Serializable]
public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization
{
public string Solution { get; }
public string Configuration { get; }
public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization
public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}");
public override bool Equals(object obj)
{
if (obj is MonoBuildInfo other)
return other.Solution == Solution && other.Configuration == Configuration;
return false;
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 29 + Solution.GetHashCode();
hash = hash * 29 + Configuration.GetHashCode();
return hash;
}
}
private MonoBuildInfo()
{
}
public MonoBuildInfo(string solution, string configuration)
{
Solution = solution;
Configuration = configuration;
}
}
}

View file

@ -0,0 +1,260 @@
using Godot;
using System;
using Godot.Collections;
using GodotTools.Internals;
using File = GodotTools.Utils.File;
using Path = System.IO.Path;
namespace GodotTools
{
public class MonoBuildTab : VBoxContainer
{
public enum BuildResults
{
Error,
Success
}
[Serializable]
private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization
{
public bool Warning { get; set; }
public string File { get; set; }
public int Line { get; set; }
public int Column { get; set; }
public string Code { get; set; }
public string Message { get; set; }
public string ProjectFile { get; set; }
}
private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization
private ItemList issuesList;
public bool BuildExited { get; private set; } = false;
public BuildResults? BuildResult { get; private set; } = null;
public int ErrorCount { get; private set; } = 0;
public int WarningCount { get; private set; } = 0;
public bool ErrorsVisible { get; set; } = true;
public bool WarningsVisible { get; set; } = true;
public Texture IconTexture
{
get
{
if (!BuildExited)
return GetIcon("Stop", "EditorIcons");
if (BuildResult == BuildResults.Error)
return GetIcon("StatusError", "EditorIcons");
return GetIcon("StatusSuccess", "EditorIcons");
}
}
public MonoBuildInfo BuildInfo { get; private set; }
private void _LoadIssuesFromFile(string csvFile)
{
using (var file = new Godot.File())
{
Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read);
if (openError != Error.Ok)
return;
while (!file.EofReached())
{
string[] csvColumns = file.GetCsvLine();
if (csvColumns.Length == 1 && csvColumns[0].Empty())
return;
if (csvColumns.Length != 7)
{
GD.PushError($"Expected 7 columns, got {csvColumns.Length}");
continue;
}
var issue = new BuildIssue
{
Warning = csvColumns[0] == "warning",
File = csvColumns[1],
Line = int.Parse(csvColumns[2]),
Column = int.Parse(csvColumns[3]),
Code = csvColumns[4],
Message = csvColumns[5],
ProjectFile = csvColumns[6]
};
if (issue.Warning)
WarningCount += 1;
else
ErrorCount += 1;
issues.Add(issue);
}
}
}
private void _IssueActivated(int idx)
{
if (idx < 0 || idx >= issuesList.GetItemCount())
throw new IndexOutOfRangeException("Item list index out of range");
// Get correct issue idx from issue list
int issueIndex = (int) issuesList.GetItemMetadata(idx);
if (idx < 0 || idx >= issues.Count)
throw new IndexOutOfRangeException("Issue index out of range");
BuildIssue issue = issues[issueIndex];
if (issue.ProjectFile.Empty() && issue.File.Empty())
return;
string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir();
string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath());
if (!File.Exists(file))
return;
file = ProjectSettings.LocalizePath(file);
if (file.StartsWith("res://"))
{
var script = (Script) ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType);
if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column))
Internal.EditorNodeShowScriptScreen();
}
}
public void UpdateIssuesList()
{
issuesList.Clear();
using (var warningIcon = GetIcon("Warning", "EditorIcons"))
using (var errorIcon = GetIcon("Error", "EditorIcons"))
{
for (int i = 0; i < issues.Count; i++)
{
BuildIssue issue = issues[i];
if (!(issue.Warning ? WarningsVisible : ErrorsVisible))
continue;
string tooltip = string.Empty;
tooltip += $"Message: {issue.Message}";
if (!issue.Code.Empty())
tooltip += $"\nCode: {issue.Code}";
tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
string text = string.Empty;
if (!issue.File.Empty())
{
text += $"{issue.File}({issue.Line},{issue.Column}): ";
tooltip += $"\nFile: {issue.File}";
tooltip += $"\nLine: {issue.Line}";
tooltip += $"\nColumn: {issue.Column}";
}
if (!issue.ProjectFile.Empty())
tooltip += $"\nProject: {issue.ProjectFile}";
text += issue.Message;
int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal);
string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx);
issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon);
int index = issuesList.GetItemCount() - 1;
issuesList.SetItemTooltip(index, tooltip);
issuesList.SetItemMetadata(index, i);
}
}
}
public void OnBuildStart()
{
BuildExited = false;
issues.Clear();
WarningCount = 0;
ErrorCount = 0;
UpdateIssuesList();
GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
}
public void OnBuildExit(BuildResults result)
{
BuildExited = true;
BuildResult = result;
_LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName));
UpdateIssuesList();
GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
}
public void OnBuildExecFailed(string cause)
{
BuildExited = true;
BuildResult = BuildResults.Error;
issuesList.Clear();
var issue = new BuildIssue {Message = cause, Warning = false};
ErrorCount += 1;
issues.Add(issue);
UpdateIssuesList();
GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
}
public void RestartBuild()
{
if (!BuildExited)
throw new InvalidOperationException("Build already started");
GodotSharpBuilds.RestartBuild(this);
}
public void StopBuild()
{
if (!BuildExited)
throw new InvalidOperationException("Build is not in progress");
GodotSharpBuilds.StopBuild(this);
}
public override void _Ready()
{
base._Ready();
issuesList = new ItemList {SizeFlagsVertical = (int) SizeFlags.ExpandFill};
issuesList.Connect("item_activated", this, nameof(_IssueActivated));
AddChild(issuesList);
}
private MonoBuildTab()
{
}
public MonoBuildTab(MonoBuildInfo buildInfo)
{
BuildInfo = buildInfo;
}
}
}

View file

@ -1,11 +1,11 @@
using GodotTools.Core;
using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using GodotTools.Internals;
namespace GodotSharpTools.Editor
namespace GodotTools
{
public class MonoDevelopInstance
{
@ -15,24 +15,24 @@ namespace GodotSharpTools.Editor
VisualStudioForMac = 1
}
readonly string solutionFile;
readonly EditorId editorId;
private readonly string solutionFile;
private readonly EditorId editorId;
Process process;
private Process process;
public void Execute(string[] files)
public void Execute(params string[] files)
{
bool newWindow = process == null || process.HasExited;
List<string> args = new List<string>();
var args = new List<string>();
string command;
if (Utils.OS.IsOSX())
{
string bundleId = codeEditorBundleIds[editorId];
string bundleId = CodeEditorBundleIds[editorId];
if (IsApplicationBundleInstalled(bundleId))
if (Internal.IsOsxAppBundleInstalled(bundleId))
{
command = "open";
@ -47,12 +47,12 @@ namespace GodotSharpTools.Editor
}
else
{
command = codeEditorPaths[editorId];
command = CodeEditorPaths[editorId];
}
}
else
{
command = codeEditorPaths[editorId];
command = CodeEditorPaths[editorId];
}
args.Add("--ipc-tcp");
@ -72,7 +72,7 @@ namespace GodotSharpTools.Editor
if (newWindow)
{
process = Process.Start(new ProcessStartInfo()
process = Process.Start(new ProcessStartInfo
{
FileName = command,
Arguments = string.Join(" ", args),
@ -81,12 +81,12 @@ namespace GodotSharpTools.Editor
}
else
{
Process.Start(new ProcessStartInfo()
Process.Start(new ProcessStartInfo
{
FileName = command,
Arguments = string.Join(" ", args),
UseShellExecute = false
});
})?.Dispose();
}
}
@ -99,45 +99,42 @@ namespace GodotSharpTools.Editor
this.editorId = editorId;
}
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static bool IsApplicationBundleInstalled(string bundleId);
static readonly IReadOnlyDictionary<EditorId, string> codeEditorPaths;
static readonly IReadOnlyDictionary<EditorId, string> codeEditorBundleIds;
private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorPaths;
private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorBundleIds;
static MonoDevelopInstance()
{
if (Utils.OS.IsOSX())
{
codeEditorPaths = new Dictionary<EditorId, string>
CodeEditorPaths = new Dictionary<EditorId, string>
{
// Rely on PATH
{ EditorId.MonoDevelop, "monodevelop" },
{ EditorId.VisualStudioForMac, "VisualStudio" }
{EditorId.MonoDevelop, "monodevelop"},
{EditorId.VisualStudioForMac, "VisualStudio"}
};
codeEditorBundleIds = new Dictionary<EditorId, string>
CodeEditorBundleIds = new Dictionary<EditorId, string>
{
// TODO EditorId.MonoDevelop
{ EditorId.VisualStudioForMac, "com.microsoft.visual-studio" }
{EditorId.VisualStudioForMac, "com.microsoft.visual-studio"}
};
}
else if (Utils.OS.IsWindows())
{
codeEditorPaths = new Dictionary<EditorId, string>
CodeEditorPaths = new Dictionary<EditorId, string>
{
// XamarinStudio is no longer a thing, and the latest version is quite old
// MonoDevelop is available from source only on Windows. The recommendation
// is to use Visual Studio instead. Since there are no official builds, we
// will rely on custom MonoDevelop builds being added to PATH.
{ EditorId.MonoDevelop, "MonoDevelop.exe" }
{EditorId.MonoDevelop, "MonoDevelop.exe"}
};
}
else if (Utils.OS.IsUnix())
{
codeEditorPaths = new Dictionary<EditorId, string>
CodeEditorPaths = new Dictionary<EditorId, string>
{
// Rely on PATH
{ EditorId.MonoDevelop, "monodevelop" }
{EditorId.MonoDevelop, "monodevelop"}
};
}
}

View file

@ -0,0 +1,26 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("GodotTools")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("Godot Engine contributors")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace GodotTools.Utils
{
public static class CollectionExtensions
{
public static T SelectFirstNotNull<T>(this IEnumerable<T> enumerable, Func<T, T> predicate, T orElse = null)
where T : class
{
foreach (T elem in enumerable)
{
if (predicate(elem) != null)
return elem;
}
return orElse;
}
}
}

View file

@ -0,0 +1,40 @@
using System.IO;
using Godot;
namespace GodotTools.Utils
{
public static class Directory
{
private static string GlobalizePath(this string path)
{
return ProjectSettings.GlobalizePath(path);
}
public static bool Exists(string path)
{
return System.IO.Directory.Exists(path.GlobalizePath());
}
/// Create directory recursively
public static DirectoryInfo CreateDirectory(string path)
{
return System.IO.Directory.CreateDirectory(path.GlobalizePath());
}
public static void Delete(string path, bool recursive)
{
System.IO.Directory.Delete(path.GlobalizePath(), recursive);
}
public static string[] GetDirectories(string path, string searchPattern, SearchOption searchOption)
{
return System.IO.Directory.GetDirectories(path.GlobalizePath(), searchPattern, searchOption);
}
public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
{
return System.IO.Directory.GetFiles(path.GlobalizePath(), searchPattern, searchOption);
}
}
}

View file

@ -0,0 +1,43 @@
using System;
using Godot;
namespace GodotTools.Utils
{
public static class File
{
private static string GlobalizePath(this string path)
{
return ProjectSettings.GlobalizePath(path);
}
public static void WriteAllText(string path, string contents)
{
System.IO.File.WriteAllText(path.GlobalizePath(), contents);
}
public static bool Exists(string path)
{
return System.IO.File.Exists(path.GlobalizePath());
}
public static DateTime GetLastWriteTime(string path)
{
return System.IO.File.GetLastWriteTime(path.GlobalizePath());
}
public static void Delete(string path)
{
System.IO.File.Delete(path.GlobalizePath());
}
public static void Copy(string sourceFileName, string destFileName)
{
System.IO.File.Copy(sourceFileName.GlobalizePath(), destFileName.GlobalizePath(), overwrite: true);
}
public static byte[] ReadAllBytes(string path)
{
return System.IO.File.ReadAllBytes(path.GlobalizePath());
}
}
}

View file

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
namespace GodotTools.Utils
{
public static class OS
{
[MethodImpl(MethodImplOptions.InternalCall)]
extern static string GetPlatformName();
const string HaikuName = "Haiku";
const string OSXName = "OSX";
const string ServerName = "Server";
const string UWPName = "UWP";
const string WindowsName = "Windows";
const string X11Name = "X11";
public static bool IsHaiku()
{
return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsOSX()
{
return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsServer()
{
return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsUWP()
{
return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsWindows()
{
return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsX11()
{
return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
private static bool? _isUnixCache;
private static readonly string[] UnixPlatforms = {HaikuName, OSXName, ServerName, X11Name};
public static bool IsUnix()
{
if (_isUnixCache.HasValue)
return _isUnixCache.Value;
string osName = GetPlatformName();
_isUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
return _isUnixCache.Value;
}
public static char PathSep => IsWindows() ? ';' : ':';
public static string PathWhich(string name)
{
string[] windowsExts = IsWindows() ? Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) : null;
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
var searchDirs = new List<string>();
if (pathDirs != null)
searchDirs.AddRange(pathDirs);
searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list
foreach (var dir in searchDirs)
{
string path = Path.Combine(dir, name);
if (IsWindows() && windowsExts != null)
{
foreach (var extension in windowsExts)
{
string pathWithExtension = path + extension;
if (File.Exists(pathWithExtension))
return pathWithExtension;
}
}
else
{
if (File.Exists(path))
return path;
}
}
return null;
}
public static void RunProcess(string command, IEnumerable<string> arguments)
{
string CmdLineArgsToString(IEnumerable<string> args)
{
return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
}
ProcessStartInfo startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments))
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
using (Process process = Process.Start(startInfo))
{
if (process == null)
throw new Exception("No process was started");
process.BeginOutputReadLine();
process.BeginErrorReadLine();
}
}
}
}

View file

@ -861,26 +861,22 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
p_output.append("\n#pragma warning restore CS1591\n");
}
Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution) {
String proj_dir = p_solution_dir.plus_file(CORE_API_ASSEMBLY_NAME);
Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
if (!DirAccess::exists(proj_dir)) {
Error err = da->make_dir_recursive(proj_dir);
if (!DirAccess::exists(p_proj_dir)) {
Error err = da->make_dir_recursive(p_proj_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
da->change_dir(proj_dir);
da->change_dir(p_proj_dir);
da->make_dir("Core");
da->make_dir("ObjectType");
String core_dir = path_join(proj_dir, "Core");
String obj_type_dir = path_join(proj_dir, "ObjectType");
Vector<String> compile_items;
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
{
@ -891,7 +887,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
if (save_err != OK)
return save_err;
compile_items.push_back(output_file);
r_compile_items.push_back(output_file);
}
for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) {
@ -909,7 +905,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
if (err != OK)
return err;
compile_items.push_back(output_file);
r_compile_items.push_back(output_file);
}
// Generate sources from compressed files
@ -939,7 +935,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
file->store_buffer(data.ptr(), data.size());
file->close();
compile_items.push_back(output_file);
r_compile_items.push_back(output_file);
}
StringBuilder cs_icalls_content;
@ -981,43 +977,27 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
if (err != OK)
return err;
compile_items.push_back(internal_methods_file);
String guid = CSharpProject::generate_core_api_project(proj_dir, compile_items);
DotNetSolution::ProjectInfo proj_info;
proj_info.guid = guid;
proj_info.relpath = String(CORE_API_ASSEMBLY_NAME).plus_file(CORE_API_ASSEMBLY_NAME ".csproj");
proj_info.configs.push_back("Debug");
proj_info.configs.push_back("Release");
r_solution.add_new_project(CORE_API_ASSEMBLY_NAME, proj_info);
_log("The solution and C# project for the Core API was generated successfully\n");
r_compile_items.push_back(internal_methods_file);
return OK;
}
Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution) {
String proj_dir = p_solution_dir.plus_file(EDITOR_API_ASSEMBLY_NAME);
Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
if (!DirAccess::exists(proj_dir)) {
Error err = da->make_dir_recursive(proj_dir);
if (!DirAccess::exists(p_proj_dir)) {
Error err = da->make_dir_recursive(p_proj_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
da->change_dir(proj_dir);
da->change_dir(p_proj_dir);
da->make_dir("Core");
da->make_dir("ObjectType");
String core_dir = path_join(proj_dir, "Core");
String obj_type_dir = path_join(proj_dir, "ObjectType");
Vector<String> compile_items;
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();
@ -1034,7 +1014,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir
if (err != OK)
return err;
compile_items.push_back(output_file);
r_compile_items.push_back(output_file);
}
StringBuilder cs_icalls_content;
@ -1077,58 +1057,56 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir
if (err != OK)
return err;
compile_items.push_back(internal_methods_file);
String guid = CSharpProject::generate_editor_api_project(proj_dir, "../" CORE_API_ASSEMBLY_NAME "/" CORE_API_ASSEMBLY_NAME ".csproj", compile_items);
DotNetSolution::ProjectInfo proj_info;
proj_info.guid = guid;
proj_info.relpath = String(EDITOR_API_ASSEMBLY_NAME).plus_file(EDITOR_API_ASSEMBLY_NAME ".csproj");
proj_info.configs.push_back("Debug");
proj_info.configs.push_back("Release");
r_solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, proj_info);
_log("The solution and C# project for the Editor API was generated successfully\n");
r_compile_items.push_back(internal_methods_file);
return OK;
}
Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {
String output_dir = DirAccess::get_full_path(p_output_dir, DirAccess::ACCESS_FILESYSTEM);
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
if (!DirAccess::exists(p_output_dir)) {
Error err = da->make_dir_recursive(p_output_dir);
if (!DirAccess::exists(output_dir)) {
Error err = da->make_dir_recursive(output_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
DotNetSolution solution(API_SOLUTION_NAME);
if (!solution.set_path(p_output_dir))
return ERR_FILE_NOT_FOUND;
Error proj_err;
proj_err = generate_cs_core_project(p_output_dir, solution);
// Generate GodotSharp source files
String core_proj_dir = output_dir.plus_file(CORE_API_ASSEMBLY_NAME);
Vector<String> core_compile_items;
proj_err = generate_cs_core_project(core_proj_dir, core_compile_items);
if (proj_err != OK) {
ERR_PRINT("Generation of the Core API C# project failed");
return proj_err;
}
proj_err = generate_cs_editor_project(p_output_dir, solution);
// Generate GodotSharpEditor source files
String editor_proj_dir = output_dir.plus_file(EDITOR_API_ASSEMBLY_NAME);
Vector<String> editor_compile_items;
proj_err = generate_cs_editor_project(editor_proj_dir, editor_compile_items);
if (proj_err != OK) {
ERR_PRINT("Generation of the Editor API C# project failed");
return proj_err;
}
Error sln_error = solution.save();
if (sln_error != OK) {
ERR_PRINT("Failed to save API solution");
return sln_error;
// Generate solution
if (!CSharpProject::generate_api_solution(output_dir,
core_proj_dir, core_compile_items, editor_proj_dir, editor_compile_items)) {
return ERR_CANT_CREATE;
}
_log("The solution for the Godot API was generated successfully\n");
return OK;
}
@ -1311,8 +1289,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
output.append(MEMBER_BEGIN "private static Godot.Object singleton;\n");
output.append(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3
"get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5
"singleton = Engine.GetSingleton(" BINDINGS_NATIVE_NAME_FIELD ");\n" INDENT4
"return singleton;\n" INDENT3 "}\n" INDENT2 "}\n");
"singleton = Engine.GetSingleton(typeof(");
output.append(itype.proxy_name);
output.append(").Name);\n" INDENT4 "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n");
output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \"");
output.append(itype.name);
@ -3018,36 +2997,49 @@ void BindingsGenerator::_initialize() {
void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {
const int NUM_OPTIONS = 2;
String mono_glue_option = "--generate-mono-glue";
String cs_api_option = "--generate-cs-api";
String generate_all_glue_option = "--generate-mono-glue";
String generate_cs_glue_option = "--generate-mono-cs-glue";
String generate_cpp_glue_option = "--generate-mono-cpp-glue";
String mono_glue_path;
String cs_api_path;
String glue_dir_path;
String cs_dir_path;
String cpp_dir_path;
int options_left = NUM_OPTIONS;
const List<String>::Element *elem = p_cmdline_args.front();
while (elem && options_left) {
if (elem->get() == mono_glue_option) {
if (elem->get() == generate_all_glue_option) {
const List<String>::Element *path_elem = elem->next();
if (path_elem) {
mono_glue_path = path_elem->get();
glue_dir_path = path_elem->get();
elem = elem->next();
} else {
ERR_PRINTS(mono_glue_option + ": No output directory specified");
ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to {GODOT_ROOT}/modules/mono/glue)");
}
--options_left;
} else if (elem->get() == cs_api_option) {
} else if (elem->get() == generate_cs_glue_option) {
const List<String>::Element *path_elem = elem->next();
if (path_elem) {
cs_api_path = path_elem->get();
cs_dir_path = path_elem->get();
elem = elem->next();
} else {
ERR_PRINTS(cs_api_option + ": No output directory specified");
ERR_PRINTS(generate_cs_glue_option + ": No output directory specified");
}
--options_left;
} else if (elem->get() == generate_cpp_glue_option) {
const List<String>::Element *path_elem = elem->next();
if (path_elem) {
cpp_dir_path = path_elem->get();
elem = elem->next();
} else {
ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified");
}
--options_left;
@ -3056,18 +3048,26 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
elem = elem->next();
}
if (mono_glue_path.length() || cs_api_path.length()) {
if (glue_dir_path.length() || cs_dir_path.length() || cpp_dir_path.length()) {
BindingsGenerator bindings_generator;
bindings_generator.set_log_print_enabled(true);
if (mono_glue_path.length()) {
if (bindings_generator.generate_glue(mono_glue_path) != OK)
ERR_PRINTS(mono_glue_option + ": Failed to generate mono glue");
if (glue_dir_path.length()) {
if (bindings_generator.generate_glue(glue_dir_path) != OK)
ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue");
if (bindings_generator.generate_cs_api(glue_dir_path.plus_file("Managed/Generated")) != OK)
ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API");
}
if (cs_api_path.length()) {
if (bindings_generator.generate_cs_api(cs_api_path) != OK)
ERR_PRINTS(cs_api_option + ": Failed to generate the C# API");
if (cs_dir_path.length()) {
if (bindings_generator.generate_cs_api(cs_dir_path) != OK)
ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API");
}
if (cpp_dir_path.length()) {
if (bindings_generator.generate_glue(cpp_dir_path) != OK)
ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue");
}
// Exit once done

View file

@ -33,7 +33,6 @@
#include "core/class_db.h"
#include "core/string_builder.h"
#include "dotnet_solution.h"
#include "editor/doc/doc_data.h"
#include "editor/editor_help.h"
@ -614,12 +613,13 @@ class BindingsGenerator {
void _initialize();
public:
Error generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution);
Error generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution);
Error generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_files);
Error generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items);
Error generate_cs_api(const String &p_output_dir);
Error generate_glue(const String &p_output_dir);
void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; }
_FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; }
_FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; }
static uint32_t get_version();

View file

@ -44,66 +44,54 @@
namespace CSharpProject {
String generate_core_api_project(const String &p_dir, const Vector<String> &p_files) {
bool generate_api_solution_impl(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items,
const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items,
GDMonoAssembly *p_tools_project_editor_assembly) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
GDMonoClass *klass = p_tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ApiSolutionGenerator");
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator");
Variant dir = p_dir;
Variant compile_items = p_files;
const Variant *args[2] = { &dir, &compile_items };
Variant solution_dir = p_solution_dir;
Variant core_proj_dir = p_core_proj_dir;
Variant core_compile_items = p_core_compile_items;
Variant editor_proj_dir = p_editor_proj_dir;
Variant editor_compile_items = p_editor_compile_items;
const Variant *args[5] = { &solution_dir, &core_proj_dir, &core_compile_items, &editor_proj_dir, &editor_compile_items };
MonoException *exc = NULL;
MonoObject *ret = klass->get_method("GenCoreApiProject", 2)->invoke(NULL, args, &exc);
klass->get_method("GenerateApiSolution", 5)->invoke(NULL, args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL_V(String());
ERR_FAIL_V(false);
}
return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String();
return true;
}
String generate_editor_api_project(const String &p_dir, const String &p_core_proj_path, const Vector<String> &p_files) {
bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items,
const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
if (GDMono::get_singleton()->get_tools_project_editor_assembly()) {
return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items,
p_editor_proj_dir, p_editor_compile_items,
GDMono::get_singleton()->get_tools_project_editor_assembly());
} else {
MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.ApiSolutionGenerationDomain");
CRASH_COND(temp_domain == NULL);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain);
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator");
_GDMONO_SCOPE_DOMAIN_(temp_domain);
Variant dir = p_dir;
Variant core_proj_path = p_core_proj_path;
Variant compile_items = p_files;
const Variant *args[3] = { &dir, &core_proj_path, &compile_items };
MonoException *exc = NULL;
MonoObject *ret = klass->get_method("GenEditorApiProject", 3)->invoke(NULL, args, &exc);
GDMonoAssembly *tools_project_editor_assembly = NULL;
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL_V(String());
if (!GDMono::get_singleton()->load_assembly("GodotTools.ProjectEditor", &tools_project_editor_assembly)) {
ERR_EXPLAIN("Failed to load assembly: 'GodotTools.ProjectEditor'");
ERR_FAIL_V(false);
}
return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items,
p_editor_proj_dir, p_editor_compile_items,
tools_project_editor_assembly);
}
return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String();
}
String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator");
Variant dir = p_dir;
Variant name = p_name;
Variant compile_items = p_files;
const Variant *args[3] = { &dir, &name, &compile_items };
MonoException *exc = NULL;
MonoObject *ret = klass->get_method("GenGameProject", 3)->invoke(NULL, args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL_V(String());
}
return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String();
}
void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) {
@ -111,9 +99,9 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str
if (!GLOBAL_DEF("mono/project/auto_update_project", true))
return;
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly();
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils");
GDMonoClass *klass = tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ProjectUtils");
Variant project_path = p_project_path;
Variant item_type = p_item_type;
@ -128,126 +116,4 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str
}
}
Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
if (FileAccess::exists(p_output_path)) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
Error rm_err = da->remove(p_output_path);
ERR_EXPLAIN("Failed to remove old scripts metadata file");
ERR_FAIL_COND_V(rm_err != OK, rm_err);
}
GDMonoClass *project_utils = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils");
void *args[2] = {
GDMonoMarshal::mono_string_from_godot(p_project_path),
GDMonoMarshal::mono_string_from_godot("Compile")
};
MonoException *exc = NULL;
MonoArray *ret = (MonoArray *)project_utils->get_method("GetIncludeFiles", 2)->invoke_raw(NULL, args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL_V(FAILED);
}
PoolStringArray project_files = GDMonoMarshal::mono_array_to_PoolStringArray(ret);
PoolStringArray::Read r = project_files.read();
Dictionary old_dict = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing();
Dictionary new_dict;
for (int i = 0; i < project_files.size(); i++) {
const String &project_file = ("res://" + r[i]).simplify_path();
uint64_t modified_time = FileAccess::get_modified_time(project_file);
const Variant *old_file_var = old_dict.getptr(project_file);
if (old_file_var) {
Dictionary old_file_dict = old_file_var->operator Dictionary();
if (old_file_dict["modified_time"].operator uint64_t() == modified_time) {
// No changes so no need to parse again
new_dict[project_file] = old_file_dict;
continue;
}
}
ScriptClassParser scp;
Error err = scp.parse_file(project_file);
if (err != OK) {
ERR_PRINTS("Parse error: " + scp.get_error());
ERR_EXPLAIN("Failed to determine namespace and class for script: " + project_file);
ERR_FAIL_V(err);
}
Vector<ScriptClassParser::ClassDecl> classes = scp.get_classes();
bool found = false;
Dictionary class_dict;
String search_name = project_file.get_file().get_basename();
for (int j = 0; j < classes.size(); j++) {
const ScriptClassParser::ClassDecl &class_decl = classes[j];
if (class_decl.base.size() == 0)
continue; // Does not inherit nor implement anything, so it can't be a script class
String class_cmp;
if (class_decl.nested) {
class_cmp = class_decl.name.get_slice(".", class_decl.name.get_slice_count(".") - 1);
} else {
class_cmp = class_decl.name;
}
if (class_cmp != search_name)
continue;
class_dict["namespace"] = class_decl.namespace_;
class_dict["class_name"] = class_decl.name;
class_dict["nested"] = class_decl.nested;
found = true;
break;
}
if (found) {
Dictionary file_dict;
file_dict["modified_time"] = modified_time;
file_dict["class"] = class_dict;
new_dict[project_file] = file_dict;
}
}
if (new_dict.size()) {
String json = JSON::print(new_dict, "", false);
String base_dir = p_output_path.get_base_dir();
if (!DirAccess::exists(base_dir)) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
Error err = da->make_dir_recursive(base_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
Error ferr;
FileAccess *f = FileAccess::open(p_output_path, FileAccess::WRITE, &ferr);
ERR_EXPLAIN("Cannot open file for writing: " + p_output_path);
ERR_FAIL_COND_V(ferr != OK, ferr);
f->store_string(json);
f->flush();
f->close();
memdelete(f);
}
return OK;
}
} // namespace CSharpProject

View file

@ -35,14 +35,11 @@
namespace CSharpProject {
String generate_core_api_project(const String &p_dir, const Vector<String> &p_files = Vector<String>());
String generate_editor_api_project(const String &p_dir, const String &p_core_proj_path, const Vector<String> &p_files = Vector<String>());
String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files = Vector<String>());
bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items,
const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items);
void add_item(const String &p_project_path, const String &p_item_type, const String &p_include);
Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path);
} // namespace CSharpProject
#endif // CSHARP_PROJECT_H

View file

@ -1,140 +0,0 @@
/*************************************************************************/
/* dotnet_solution.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "dotnet_solution.h"
#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "../utils/path_utils.h"
#include "../utils/string_utils.h"
#include "csharp_project.h"
#define SOLUTION_TEMPLATE \
"Microsoft Visual Studio Solution File, Format Version 12.00\n" \
"# Visual Studio 2012\n" \
"%0\n" \
"Global\n" \
"\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n" \
"%1\n" \
"\tEndGlobalSection\n" \
"\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n" \
"%2\n" \
"\tEndGlobalSection\n" \
"EndGlobal\n"
#define PROJECT_DECLARATION "Project(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"%0\", \"%1\", \"{%2}\"\nEndProject"
#define SOLUTION_PLATFORMS_CONFIG "\t%0|Any CPU = %0|Any CPU"
#define PROJECT_PLATFORMS_CONFIG \
"\t\t{%0}.%1|Any CPU.ActiveCfg = %1|Any CPU\n" \
"\t\t{%0}.%1|Any CPU.Build.0 = %1|Any CPU"
void DotNetSolution::add_new_project(const String &p_name, const ProjectInfo &p_project_info) {
projects[p_name] = p_project_info;
}
bool DotNetSolution::has_project(const String &p_name) const {
return projects.find(p_name) != NULL;
}
const DotNetSolution::ProjectInfo &DotNetSolution::get_project_info(const String &p_name) const {
return projects[p_name];
}
bool DotNetSolution::remove_project(const String &p_name) {
return projects.erase(p_name);
}
Error DotNetSolution::save() {
bool dir_exists = DirAccess::exists(path);
ERR_EXPLAIN("The directory does not exist.");
ERR_FAIL_COND_V(!dir_exists, ERR_FILE_NOT_FOUND);
String projs_decl;
String sln_platform_cfg;
String proj_platform_cfg;
for (Map<String, ProjectInfo>::Element *E = projects.front(); E; E = E->next()) {
const String &name = E->key();
const ProjectInfo &proj_info = E->value();
bool is_front = E == projects.front();
if (!is_front)
projs_decl += "\n";
projs_decl += sformat(PROJECT_DECLARATION, name, proj_info.relpath.replace("/", "\\"), proj_info.guid);
for (int i = 0; i < proj_info.configs.size(); i++) {
const String &config = proj_info.configs[i];
if (i != 0 || !is_front) {
sln_platform_cfg += "\n";
proj_platform_cfg += "\n";
}
sln_platform_cfg += sformat(SOLUTION_PLATFORMS_CONFIG, config);
proj_platform_cfg += sformat(PROJECT_PLATFORMS_CONFIG, proj_info.guid, config);
}
}
String content = sformat(SOLUTION_TEMPLATE, projs_decl, sln_platform_cfg, proj_platform_cfg);
FileAccess *file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE);
ERR_FAIL_NULL_V(file, ERR_FILE_CANT_WRITE);
file->store_string(content);
file->close();
memdelete(file);
return OK;
}
bool DotNetSolution::set_path(const String &p_existing_path) {
if (p_existing_path.is_abs_path()) {
path = p_existing_path;
} else {
String abspath;
if (!rel_path_to_abs(p_existing_path, abspath))
return false;
path = abspath;
}
return true;
}
String DotNetSolution::get_path() {
return path;
}
DotNetSolution::DotNetSolution(const String &p_name) {
name = p_name;
}

View file

@ -1,63 +0,0 @@
/*************************************************************************/
/* dotnet_solution.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 NET_SOLUTION_H
#define NET_SOLUTION_H
#include "core/map.h"
#include "core/ustring.h"
struct DotNetSolution {
String name;
struct ProjectInfo {
String guid;
String relpath; // Must be relative to the solution directory
Vector<String> configs;
};
void add_new_project(const String &p_name, const ProjectInfo &p_project_info);
bool has_project(const String &p_name) const;
const ProjectInfo &get_project_info(const String &p_name) const;
bool remove_project(const String &p_name);
Error save();
bool set_path(const String &p_existing_path);
String get_path();
DotNetSolution(const String &p_name);
private:
String path;
Map<String, ProjectInfo> projects;
};
#endif // NET_SOLUTION_H

View file

@ -0,0 +1,429 @@
/*************************************************************************/
/* editor_internal_calls.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "editor_internal_calls.h"
#include "core/message_queue.h"
#include "core/os/os.h"
#include "core/version.h"
#include "editor/editor_node.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/script_editor_debugger.h"
#include "main/main.h"
#include "../csharp_script.h"
#include "../glue/cs_glue_version.gen.h"
#include "../godotsharp_dirs.h"
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/osx_utils.h"
#include "bindings_generator.h"
#include "godotsharp_export.h"
#include "script_class_parser.h"
MonoString *godot_icall_GodotSharpDirs_ResDataDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResMetadataDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_metadata_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResAssembliesBaseDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_base_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResAssembliesDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResConfigDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_config_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResTempDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_base_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_dir());
}
MonoString *godot_icall_GodotSharpDirs_MonoUserDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_user_dir());
}
MonoString *godot_icall_GodotSharpDirs_MonoLogsDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_logs_dir());
}
MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_DataMonoEtcDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_etc_dir());
}
MonoString *godot_icall_GodotSharpDirs_DataMonoLibDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_lib_dir());
}
MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() {
#ifdef WINDOWS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir());
#else
return NULL;
#endif
}
void godot_icall_EditorProgress_Create(MonoString *p_task, MonoString *p_label, int32_t p_amount, MonoBoolean p_can_cancel) {
String task = GDMonoMarshal::mono_string_to_godot(p_task);
String label = GDMonoMarshal::mono_string_to_godot(p_label);
EditorNode::progress_add_task(task, label, p_amount, (bool)p_can_cancel);
}
void godot_icall_EditorProgress_Dispose(MonoString *p_task) {
String task = GDMonoMarshal::mono_string_to_godot(p_task);
EditorNode::progress_end_task(task);
}
MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_state, int32_t p_step, MonoBoolean p_force_refresh) {
String task = GDMonoMarshal::mono_string_to_godot(p_task);
String state = GDMonoMarshal::mono_string_to_godot(p_state);
return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh);
}
BindingsGenerator *godot_icall_BindingsGenerator_Ctor() {
return memnew(BindingsGenerator);
}
void godot_icall_BindingsGenerator_Dtor(BindingsGenerator *p_handle) {
memdelete(p_handle);
}
MonoBoolean godot_icall_BindingsGenerator_LogPrintEnabled(BindingsGenerator *p_handle) {
return p_handle->is_log_print_enabled();
}
void godot_icall_BindingsGenerator_SetLogPrintEnabled(BindingsGenerator p_handle, MonoBoolean p_enabled) {
p_handle.set_log_print_enabled(p_enabled);
}
int32_t godot_icall_BindingsGenerator_GenerateCsApi(BindingsGenerator *p_handle, MonoString *p_output_dir) {
String output_dir = GDMonoMarshal::mono_string_to_godot(p_output_dir);
return p_handle->generate_cs_api(output_dir);
}
uint32_t godot_icall_BindingsGenerator_Version() {
return BindingsGenerator::get_version();
}
uint32_t godot_icall_BindingsGenerator_CsGlueVersion() {
return CS_GLUE_VERSION;
}
int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes) {
String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath);
ScriptClassParser scp;
Error err = scp.parse_file(filepath);
if (err == OK) {
Array classes = GDMonoMarshal::mono_object_to_variant(p_classes);
const Vector<ScriptClassParser::ClassDecl> &class_decls = scp.get_classes();
for (int i = 0; i < class_decls.size(); i++) {
const ScriptClassParser::ClassDecl &classDecl = class_decls[i];
Dictionary classDeclDict;
classDeclDict["name"] = classDecl.name;
classDeclDict["namespace"] = classDecl.namespace_;
classDeclDict["nested"] = classDecl.nested;
classDeclDict["base_count"] = classDecl.base.size();
classes.push_back(classDeclDict);
}
}
return err;
}
uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString *p_project_dll_name, MonoString *p_project_dll_src_path,
MonoString *p_build_config, MonoString *p_custom_lib_dir, MonoObject *r_dependencies) {
String project_dll_name = GDMonoMarshal::mono_string_to_godot(p_project_dll_name);
String project_dll_src_path = GDMonoMarshal::mono_string_to_godot(p_project_dll_src_path);
String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config);
String custom_lib_dir = GDMonoMarshal::mono_string_to_godot(p_custom_lib_dir);
Dictionary dependencies = GDMonoMarshal::mono_object_to_variant(r_dependencies);
return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_lib_dir, dependencies);
}
float godot_icall_Internal_EditorScale() {
return EDSCALE;
}
MonoObject *godot_icall_Internal_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
}
MonoObject *godot_icall_Internal_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
}
MonoString *godot_icall_Internal_FullTemplatesDir() {
String full_templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);
return GDMonoMarshal::mono_string_from_godot(full_templates_dir);
}
MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) {
String path = GDMonoMarshal::mono_string_to_godot(p_path);
return GDMonoMarshal::mono_string_from_godot(path.simplify_path());
}
MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id) {
#ifdef OSX_ENABLED
String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id);
return (MonoBoolean)osx_is_app_bundle_installed;
#else
(void)p_bundle_id; // UNUSED
return (MonoBoolean) false;
#endif
}
MonoBoolean godot_icall_Internal_MetadataIsApiAssemblyInvalidated(int32_t p_api_type) {
return GDMono::get_singleton()->metadata_is_api_assembly_invalidated((APIAssembly::Type)p_api_type);
}
void godot_icall_Internal_MetadataSetApiAssemblyInvalidated(int32_t p_api_type, MonoBoolean p_invalidated) {
GDMono::get_singleton()->metadata_set_api_assembly_invalidated((APIAssembly::Type)p_api_type, (bool)p_invalidated);
}
MonoBoolean godot_icall_Internal_IsMessageQueueFlushing() {
return (MonoBoolean)MessageQueue::get_singleton()->is_flushing();
}
MonoBoolean godot_icall_Internal_GodotIs32Bits() {
return sizeof(void *) == 4;
}
MonoBoolean godot_icall_Internal_GodotIsRealTDouble() {
#ifdef REAL_T_IS_DOUBLE
return (MonoBoolean) true;
#else
return (MonoBoolean) false;
#endif
}
void godot_icall_Internal_GodotMainIteration() {
Main::iteration();
}
uint64_t godot_icall_Internal_GetCoreApiHash() {
return ClassDB::get_api_hash(ClassDB::API_CORE);
}
uint64_t godot_icall_Internal_GetEditorApiHash() {
return ClassDB::get_api_hash(ClassDB::API_EDITOR);
}
MonoBoolean godot_icall_Internal_IsAssembliesReloadingNeeded() {
#ifdef GD_MONO_HOT_RELOAD
return (MonoBoolean)CSharpLanguage::get_singleton()->is_assembly_reloading_needed();
#else
return (MonoBoolean) false;
#endif
}
void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) {
#ifdef GD_MONO_HOT_RELOAD
_GodotSharp::get_singleton()->call_deferred("_reload_assemblies", (bool)p_soft_reload);
#endif
}
void godot_icall_Internal_ScriptEditorDebuggerReloadScripts() {
ScriptEditor::get_singleton()->get_debugger()->reload_scripts();
}
MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) {
Ref<Resource> resource = GDMonoMarshal::mono_object_to_variant(p_resource);
return (MonoBoolean)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus);
}
void godot_icall_Internal_EditorNodeShowScriptScreen() {
EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT);
}
MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) {
Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing();
MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype);
uint32_t type_encoding = mono_type_get_type(dict_type);
MonoClass *type_class_raw = mono_class_from_mono_type(dict_type);
GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw);
return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class));
}
MonoString *godot_icall_Internal_MonoWindowsInstallRoot() {
#ifdef WINDOWS_ENABLED
String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir;
return GDMonoMarshal::mono_string_from_godot(install_root_dir);
#else
return NULL;
#endif
}
MonoString *godot_icall_Utils_OS_GetPlatformName() {
String os_name = OS::get_singleton()->get_name();
return GDMonoMarshal::mono_string_from_godot(os_name);
}
void register_editor_internal_calls() {
// GodotSharpDirs
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", (void *)godot_icall_GodotSharpDirs_ResDataDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", (void *)godot_icall_GodotSharpDirs_ResMetadataDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesBaseDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", (void *)godot_icall_GodotSharpDirs_ResConfigDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", (void *)godot_icall_GodotSharpDirs_ResTempDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", (void *)godot_icall_GodotSharpDirs_MonoUserDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", (void *)godot_icall_GodotSharpDirs_MonoLogsDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", (void *)godot_icall_GodotSharpDirs_MonoSolutionsDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", (void *)godot_icall_GodotSharpDirs_BuildLogsDirs);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", (void *)godot_icall_GodotSharpDirs_ProjectSlnPath);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", (void *)godot_icall_GodotSharpDirs_ProjectCsProjPath);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", (void *)godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", (void *)godot_icall_GodotSharpDirs_DataMonoEtcDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", (void *)godot_icall_GodotSharpDirs_DataMonoLibDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", (void *)godot_icall_GodotSharpDirs_DataMonoBinDir);
// EditorProgress
mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", (void *)godot_icall_EditorProgress_Create);
mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", (void *)godot_icall_EditorProgress_Dispose);
mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", (void *)godot_icall_EditorProgress_Step);
// BiningsGenerator
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Ctor", (void *)godot_icall_BindingsGenerator_Ctor);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Dtor", (void *)godot_icall_BindingsGenerator_Dtor);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_LogPrintEnabled", (void *)godot_icall_BindingsGenerator_LogPrintEnabled);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_SetLogPrintEnabled", (void *)godot_icall_BindingsGenerator_SetLogPrintEnabled);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_GenerateCsApi", (void *)godot_icall_BindingsGenerator_GenerateCsApi);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Version", (void *)godot_icall_BindingsGenerator_Version);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_CsGlueVersion", (void *)godot_icall_BindingsGenerator_CsGlueVersion);
// ScriptClassParser
mono_add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", (void *)godot_icall_ScriptClassParser_ParseFile);
// GodotSharpExport
mono_add_internal_call("GodotTools.GodotSharpExport::internal_GetExportedAssemblyDependencies", (void *)godot_icall_GodotSharpExport_GetExportedAssemblyDependencies);
// Internals
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorScale", (void *)godot_icall_Internal_EditorScale);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GlobalDef", (void *)godot_icall_Internal_GlobalDef);
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorDef", (void *)godot_icall_Internal_EditorDef);
mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir);
mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath);
mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled);
mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataIsApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataIsApiAssemblyInvalidated);
mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataSetApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataSetApiAssemblyInvalidated);
mono_add_internal_call("GodotTools.Internals.Internal::internal_IsMessageQueueFlushing", (void *)godot_icall_Internal_IsMessageQueueFlushing);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", (void *)godot_icall_Internal_GetCoreApiHash);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", (void *)godot_icall_Internal_GetEditorApiHash);
mono_add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", (void *)godot_icall_Internal_IsAssembliesReloadingNeeded);
mono_add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", (void *)godot_icall_Internal_ReloadAssemblies);
mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebuggerReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebuggerReloadScripts);
mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit);
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing);
mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot);
// Utils.OS
mono_add_internal_call("GodotTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName);
}

View file

@ -1,5 +1,5 @@
/*************************************************************************/
/* mono_build_info.h */
/* editor_internal_calls.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,28 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef MONO_BUILD_INFO_H
#define MONO_BUILD_INFO_H
#ifndef EDITOR_INTERNAL_CALL_H
#define EDITOR_INTERNAL_CALL_H
#include "core/ustring.h"
#include "core/vector.h"
void register_editor_internal_calls();
struct MonoBuildInfo {
struct Hasher {
static uint32_t hash(const MonoBuildInfo &p_key);
};
String solution;
String configuration;
Vector<String> custom_props;
bool operator==(const MonoBuildInfo &p_b) const;
String get_log_dirpath();
MonoBuildInfo();
MonoBuildInfo(const String &p_solution, const String &p_config);
};
#endif // MONO_BUILD_INFO_H
#endif // EDITOR_INTERNAL_CALL_H

View file

@ -1,632 +0,0 @@
/*************************************************************************/
/* godotsharp_builds.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "godotsharp_builds.h"
#include "core/os/os.h"
#include "core/vector.h"
#include "main/main.h"
#include "../glue/cs_glue_version.gen.h"
#include "../godotsharp_dirs.h"
#include "../mono_gd/gd_mono_class.h"
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/path_utils.h"
#include "bindings_generator.h"
#include "csharp_project.h"
#include "godotsharp_editor.h"
#define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)"
#define PROP_NAME_MSBUILD_VS "MSBuild (VS Build Tools)"
#define PROP_NAME_XBUILD "xbuild (Deprecated)"
void godot_icall_BuildInstance_ExitCallback(MonoString *p_solution, MonoString *p_config, int p_exit_code) {
String solution = GDMonoMarshal::mono_string_to_godot(p_solution);
String config = GDMonoMarshal::mono_string_to_godot(p_config);
GodotSharpBuilds::get_singleton()->build_exit_callback(MonoBuildInfo(solution, config), p_exit_code);
}
static Vector<const char *> _get_msbuild_hint_dirs() {
Vector<const char *> ret;
#ifdef OSX_ENABLED
ret.push_back("/Library/Frameworks/Mono.framework/Versions/Current/bin/");
ret.push_back("/usr/local/var/homebrew/linked/mono/bin/");
#endif
ret.push_back("/opt/novell/mono/bin/");
return ret;
}
#ifdef UNIX_ENABLED
String _find_build_engine_on_unix(const String &p_name) {
String ret = path_which(p_name);
if (ret.length())
return ret;
String ret_fallback = path_which(p_name + ".exe");
if (ret_fallback.length())
return ret_fallback;
static Vector<const char *> locations = _get_msbuild_hint_dirs();
for (int i = 0; i < locations.size(); i++) {
String hint_path = locations[i] + p_name;
if (FileAccess::exists(hint_path)) {
return hint_path;
}
}
return String();
}
#endif
MonoString *godot_icall_BuildInstance_get_MSBuildPath() {
GodotSharpBuilds::BuildTool build_tool = GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool")));
#if defined(WINDOWS_ENABLED)
switch (build_tool) {
case GodotSharpBuilds::MSBUILD_VS: {
static String msbuild_tools_path;
if (msbuild_tools_path.empty() || !FileAccess::exists(msbuild_tools_path)) {
// Try to search it again if it wasn't found last time or if it was removed from its location
msbuild_tools_path = MonoRegUtils::find_msbuild_tools_path();
if (msbuild_tools_path.empty()) {
ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Tried with path: " + msbuild_tools_path);
return NULL;
}
}
if (!msbuild_tools_path.ends_with("\\"))
msbuild_tools_path += "\\";
return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe");
} break;
case GodotSharpBuilds::MSBUILD_MONO: {
String msbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("msbuild.bat");
if (!FileAccess::exists(msbuild_path)) {
ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_MONO "'. Tried with path: " + msbuild_path);
return NULL;
}
return GDMonoMarshal::mono_string_from_godot(msbuild_path);
} break;
case GodotSharpBuilds::XBUILD: {
String xbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("xbuild.bat");
if (!FileAccess::exists(xbuild_path)) {
ERR_PRINTS("Cannot find executable for '" PROP_NAME_XBUILD "'. Tried with path: " + xbuild_path);
return NULL;
}
return GDMonoMarshal::mono_string_from_godot(xbuild_path);
} break;
default:
ERR_EXPLAIN("You don't deserve to live");
CRASH_NOW();
}
#elif defined(UNIX_ENABLED)
static String msbuild_path;
static String xbuild_path;
if (build_tool == GodotSharpBuilds::XBUILD) {
if (xbuild_path.empty() || !FileAccess::exists(xbuild_path)) {
// Try to search it again if it wasn't found last time or if it was removed from its location
xbuild_path = _find_build_engine_on_unix("msbuild");
}
if (xbuild_path.empty()) {
ERR_PRINT("Cannot find binary for '" PROP_NAME_XBUILD "'");
return NULL;
}
} else {
if (msbuild_path.empty() || !FileAccess::exists(msbuild_path)) {
// Try to search it again if it wasn't found last time or if it was removed from its location
msbuild_path = _find_build_engine_on_unix("msbuild");
}
if (msbuild_path.empty()) {
ERR_PRINT("Cannot find binary for '" PROP_NAME_MSBUILD_MONO "'");
return NULL;
}
}
return GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path);
#else
(void)build_tool; // UNUSED
ERR_EXPLAIN("Not implemented on this platform");
ERR_FAIL_V(NULL);
#endif
}
MonoString *godot_icall_BuildInstance_get_MonoWindowsBinDir() {
#if defined(WINDOWS_ENABLED)
const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info();
if (mono_reg_info.bin_dir.length()) {
return GDMonoMarshal::mono_string_from_godot(mono_reg_info.bin_dir);
}
ERR_EXPLAIN("Cannot find Mono's binaries directory in the registry");
ERR_FAIL_V(NULL);
#else
return NULL;
#endif
}
MonoBoolean godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows() {
#if defined(WINDOWS_ENABLED)
return GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))) == GodotSharpBuilds::MSBUILD_MONO;
#else
return false;
#endif
}
MonoBoolean godot_icall_BuildInstance_get_PrintBuildOutput() {
return (bool)EDITOR_GET("mono/builds/print_build_output");
}
void GodotSharpBuilds::register_internal_calls() {
static bool registered = false;
ERR_FAIL_COND(registered);
registered = true;
mono_add_internal_call("GodotSharpTools.Build.BuildSystem::godot_icall_BuildInstance_ExitCallback", (void *)godot_icall_BuildInstance_ExitCallback);
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildPath", (void *)godot_icall_BuildInstance_get_MSBuildPath);
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MonoWindowsBinDir", (void *)godot_icall_BuildInstance_get_MonoWindowsBinDir);
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows", (void *)godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows);
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_PrintBuildOutput", (void *)godot_icall_BuildInstance_get_PrintBuildOutput);
}
void GodotSharpBuilds::show_build_error_dialog(const String &p_message) {
GodotSharpEditor::get_singleton()->show_error_dialog(p_message, "Build error");
MonoBottomPanel::get_singleton()->show_build_tab();
}
bool GodotSharpBuilds::build_api_sln(const String &p_api_sln_dir, const String &p_config) {
String api_sln_file = p_api_sln_dir.plus_file(API_SOLUTION_NAME ".sln");
String core_api_assembly_dir = p_api_sln_dir.plus_file(CORE_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config);
String core_api_assembly_file = core_api_assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String editor_api_assembly_dir = p_api_sln_dir.plus_file(EDITOR_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config);
String editor_api_assembly_file = editor_api_assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
if (!FileAccess::exists(core_api_assembly_file) || !FileAccess::exists(editor_api_assembly_file)) {
MonoBuildInfo api_build_info(api_sln_file, p_config);
// TODO Replace this global NoWarn with '#pragma warning' directives on generated files,
// once we start to actively document manually maintained C# classes
api_build_info.custom_props.push_back("NoWarn=1591"); // Ignore missing documentation warnings
if (!GodotSharpBuilds::get_singleton()->build(api_build_info)) {
show_build_error_dialog("Failed to build " API_SOLUTION_NAME " solution.");
return false;
}
}
return true;
}
bool GodotSharpBuilds::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
if (!DirAccess::exists(p_dst_dir)) {
DirAccess *da = DirAccess::create_for_path(p_dst_dir);
Error err = da->make_dir_recursive(p_dst_dir);
memdelete(da);
if (err != OK) {
show_build_error_dialog("Failed to create destination directory for the API assemblies. Error: " + itos(err));
return false;
}
}
String assembly_file = p_assembly_name + ".dll";
String assembly_src = p_src_dir.plus_file(assembly_file);
String assembly_dst = p_dst_dir.plus_file(assembly_file);
if (!FileAccess::exists(assembly_dst) ||
FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) ||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String xml_file = p_assembly_name + ".xml";
if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK)
WARN_PRINTS("Failed to copy " + xml_file);
String pdb_file = p_assembly_name + ".pdb";
if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK)
WARN_PRINTS("Failed to copy " + pdb_file);
Error err = da->copy(assembly_src, assembly_dst);
if (err != OK) {
show_build_error_dialog("Failed to copy " + assembly_file);
return false;
}
GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false);
}
return true;
}
String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) {
uint64_t api_hash = p_api_type == APIAssembly::API_CORE ?
GDMono::get_singleton()->get_api_core_hash() :
GDMono::get_singleton()->get_api_editor_hash();
return String::num_uint64(api_hash) +
"_" + String::num_uint64(BindingsGenerator::get_version()) +
"_" + String::num_uint64(CS_GLUE_VERSION);
}
bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) {
String api_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
String editor_prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir();
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
if (FileAccess::exists(editor_prebuilt_api_dir.plus_file(api_name + ".dll"))) {
EditorProgress pr("mono_copy_prebuilt_api_assembly", "Copying prebuilt " + api_name + " assembly...", 1);
pr.step("Copying " + api_name + " assembly", 0);
return GodotSharpBuilds::copy_api_assembly(editor_prebuilt_api_dir, res_assemblies_dir, api_name, p_api_type);
}
String api_build_config = "Release";
EditorProgress pr("mono_build_release_" API_SOLUTION_NAME, "Building " API_SOLUTION_NAME " solution...", 3);
pr.step("Generating " API_SOLUTION_NAME " solution", 0);
String api_sln_dir = GodotSharpDirs::get_mono_solutions_dir()
.plus_file(_api_folder_name(APIAssembly::API_CORE));
String api_sln_file = api_sln_dir.plus_file(API_SOLUTION_NAME ".sln");
if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) {
BindingsGenerator bindings_generator;
if (!OS::get_singleton()->is_stdout_verbose()) {
bindings_generator.set_log_print_enabled(false);
}
Error err = bindings_generator.generate_cs_api(api_sln_dir);
if (err != OK) {
show_build_error_dialog("Failed to generate " API_SOLUTION_NAME " solution. Error: " + itos(err));
return false;
}
}
pr.step("Building " API_SOLUTION_NAME " solution", 1);
if (!GodotSharpBuilds::build_api_sln(api_sln_dir, api_build_config))
return false;
pr.step("Copying " + api_name + " assembly", 2);
// Copy the built assembly to the assemblies directory
String api_assembly_dir = api_sln_dir.plus_file(api_name).plus_file("bin").plus_file(api_build_config);
if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name, p_api_type))
return false;
return true;
}
bool GodotSharpBuilds::build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines) {
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
return true; // No solution to build
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE))
return false;
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR))
return false;
EditorProgress pr("mono_project_debug_build", "Building project solution...", 1);
pr.step("Building project solution", 0);
MonoBuildInfo build_info(GodotSharpDirs::get_project_sln_path(), p_config);
// Add Godot defines
#ifdef WINDOWS_ENABLED
String constants = "GodotDefineConstants=\"";
#else
String constants = "GodotDefineConstants=\\\"";
#endif
for (int i = 0; i < p_godot_defines.size(); i++) {
constants += "GODOT_" + p_godot_defines[i].to_upper().replace("-", "_").replace(" ", "_").replace(";", "_") + ";";
}
#ifdef REAL_T_IS_DOUBLE
constants += "GODOT_REAL_T_IS_DOUBLE;";
#endif
#ifdef WINDOWS_ENABLED
constants += "\"";
#else
constants += "\\\"";
#endif
build_info.custom_props.push_back(constants);
if (!GodotSharpBuilds::get_singleton()->build(build_info)) {
GodotSharpBuilds::show_build_error_dialog("Failed to build project solution");
return false;
}
return true;
}
bool GodotSharpBuilds::editor_build_callback() {
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
return true; // No solution to build
String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor");
String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player");
Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor);
ERR_FAIL_COND_V(metadata_err != OK, false);
if (FileAccess::exists(scripts_metadata_path_editor)) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player);
ERR_EXPLAIN("Failed to copy scripts metadata file");
ERR_FAIL_COND_V(copy_err != OK, false);
}
Vector<String> godot_defines;
godot_defines.push_back(OS::get_singleton()->get_name());
godot_defines.push_back(sizeof(void *) == 4 ? "32" : "64");
return build_project_blocking("Tools", godot_defines);
}
GodotSharpBuilds *GodotSharpBuilds::singleton = NULL;
void GodotSharpBuilds::build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code) {
BuildProcess *match = builds.getptr(p_build_info);
ERR_FAIL_NULL(match);
BuildProcess &bp = *match;
bp.on_exit(p_exit_code);
}
void GodotSharpBuilds::restart_build(MonoBuildTab *p_build_tab) {
}
void GodotSharpBuilds::stop_build(MonoBuildTab *p_build_tab) {
}
bool GodotSharpBuilds::build(const MonoBuildInfo &p_build_info) {
BuildProcess *match = builds.getptr(p_build_info);
if (match) {
BuildProcess &bp = *match;
bp.start(true);
return bp.exit_code == 0;
} else {
BuildProcess bp = BuildProcess(p_build_info);
bp.start(true);
builds.set(p_build_info, bp);
return bp.exit_code == 0;
}
}
bool GodotSharpBuilds::build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) {
BuildProcess *match = builds.getptr(p_build_info);
if (match) {
BuildProcess &bp = *match;
bp.start();
return !bp.exited; // failed to start
} else {
BuildProcess bp = BuildProcess(p_build_info, p_callback);
bp.start();
builds.set(p_build_info, bp);
return !bp.exited; // failed to start
}
}
GodotSharpBuilds::GodotSharpBuilds() {
singleton = this;
EditorNode::get_singleton()->add_build_callback(&GodotSharpBuilds::editor_build_callback);
// Build tool settings
EditorSettings *ed_settings = EditorSettings::get_singleton();
#ifdef WINDOWS_ENABLED
EDITOR_DEF("mono/builds/build_tool", MSBUILD_VS);
#else
EDITOR_DEF("mono/builds/build_tool", MSBUILD_MONO);
#endif
ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM,
PROP_NAME_MSBUILD_MONO
#ifdef WINDOWS_ENABLED
"," PROP_NAME_MSBUILD_VS
#endif
"," PROP_NAME_XBUILD));
EDITOR_DEF("mono/builds/print_build_output", false);
}
GodotSharpBuilds::~GodotSharpBuilds() {
singleton = NULL;
}
void GodotSharpBuilds::BuildProcess::on_exit(int p_exit_code) {
exited = true;
exit_code = p_exit_code;
build_tab->on_build_exit(p_exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR);
build_instance.unref();
if (exit_callback)
exit_callback(exit_code);
}
void GodotSharpBuilds::BuildProcess::start(bool p_blocking) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
exit_code = -1;
String log_dirpath = build_info.get_log_dirpath();
if (build_tab) {
build_tab->on_build_start();
} else {
build_tab = memnew(MonoBuildTab(build_info, log_dirpath));
MonoBottomPanel::get_singleton()->add_build_tab(build_tab);
}
if (p_blocking) {
// Required in order to update the build tasks list
Main::iteration();
}
if (!exited) {
exited = true;
String message = "Tried to start build process, but it is already running";
build_tab->on_build_exec_failed(message);
ERR_EXPLAIN(message);
ERR_FAIL();
}
exited = false;
// Remove old issues file
String issues_file = get_msbuild_issues_filename();
DirAccessRef d = DirAccess::create_for_path(log_dirpath);
if (d->file_exists(issues_file)) {
Error err = d->remove(issues_file);
if (err != OK) {
exited = true;
String file_path = ProjectSettings::get_singleton()->localize_path(log_dirpath).plus_file(issues_file);
String message = "Cannot remove issues file: " + file_path;
build_tab->on_build_exec_failed(message);
ERR_EXPLAIN(message);
ERR_FAIL();
}
}
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Build", "BuildInstance");
MonoObject *mono_object = mono_object_new(mono_domain_get(), klass->get_mono_ptr());
// Construct
Variant solution = build_info.solution;
Variant config = build_info.configuration;
const Variant *ctor_args[2] = { &solution, &config };
MonoException *exc = NULL;
GDMonoMethod *ctor = klass->get_method(".ctor", 2);
ctor->invoke(mono_object, ctor_args, &exc);
if (exc) {
exited = true;
GDMonoUtils::debug_unhandled_exception(exc);
String message = "The build constructor threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc);
build_tab->on_build_exec_failed(message);
ERR_EXPLAIN(message);
ERR_FAIL();
}
// Call Build
String logger_assembly_path = GDMono::get_singleton()->get_editor_tools_assembly()->get_path();
Variant logger_assembly = ProjectSettings::get_singleton()->globalize_path(logger_assembly_path);
Variant logger_output_dir = log_dirpath;
Variant custom_props = build_info.custom_props;
const Variant *args[3] = { &logger_assembly, &logger_output_dir, &custom_props };
exc = NULL;
GDMonoMethod *build_method = klass->get_method(p_blocking ? "Build" : "BuildAsync", 3);
build_method->invoke(mono_object, args, &exc);
if (exc) {
exited = true;
GDMonoUtils::debug_unhandled_exception(exc);
String message = "The build method threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc);
build_tab->on_build_exec_failed(message);
ERR_EXPLAIN(message);
ERR_FAIL();
}
// Build returned
if (p_blocking) {
exited = true;
exit_code = klass->get_field("exitCode")->get_int_value(mono_object);
if (exit_code != 0) {
String log_filepath = build_info.get_log_dirpath().plus_file(get_msbuild_log_filename());
print_verbose("MSBuild exited with code: " + itos(exit_code) + ". Log file: " + log_filepath);
}
build_tab->on_build_exit(exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR);
} else {
build_instance = MonoGCHandle::create_strong(mono_object);
exited = false;
}
}
GodotSharpBuilds::BuildProcess::BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) :
build_info(p_build_info),
build_tab(NULL),
exit_callback(p_callback),
exited(true),
exit_code(-1) {
}

View file

@ -1,103 +0,0 @@
/*************************************************************************/
/* godotsharp_builds.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 GODOTSHARP_BUILDS_H
#define GODOTSHARP_BUILDS_H
#include "../mono_gd/gd_mono.h"
#include "mono_bottom_panel.h"
#include "mono_build_info.h"
typedef void (*GodotSharpBuild_ExitCallback)(int);
class GodotSharpBuilds {
private:
struct BuildProcess {
Ref<MonoGCHandle> build_instance;
MonoBuildInfo build_info;
MonoBuildTab *build_tab;
GodotSharpBuild_ExitCallback exit_callback;
bool exited;
int exit_code;
void on_exit(int p_exit_code);
void start(bool p_blocking = false);
BuildProcess() {}
BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL);
};
HashMap<MonoBuildInfo, BuildProcess, MonoBuildInfo::Hasher> builds;
static String _api_folder_name(APIAssembly::Type p_api_type);
static GodotSharpBuilds *singleton;
public:
enum BuildTool {
MSBUILD_MONO,
#ifdef WINDOWS_ENABLED
MSBUILD_VS,
#endif
XBUILD // Deprecated
};
_FORCE_INLINE_ static GodotSharpBuilds *get_singleton() { return singleton; }
static void register_internal_calls();
static void show_build_error_dialog(const String &p_message);
static const char *get_msbuild_issues_filename() { return "msbuild_issues.csv"; }
static const char *get_msbuild_log_filename() { return "msbuild_log.txt"; }
void build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code);
void restart_build(MonoBuildTab *p_build_tab);
void stop_build(MonoBuildTab *p_build_tab);
bool build(const MonoBuildInfo &p_build_info);
bool build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL);
static bool build_api_sln(const String &p_api_sln_dir, const String &p_config);
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);
static bool make_api_assembly(APIAssembly::Type p_api_type);
static bool build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines);
static bool editor_build_callback();
GodotSharpBuilds();
~GodotSharpBuilds();
};
#endif // GODOTSHARP_BUILDS_H

View file

@ -1,582 +0,0 @@
/*************************************************************************/
/* godotsharp_editor.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "godotsharp_editor.h"
#include "core/message_queue.h"
#include "core/os/os.h"
#include "core/project_settings.h"
#include "scene/gui/control.h"
#include "scene/main/node.h"
#include "../csharp_script.h"
#include "../godotsharp_dirs.h"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/path_utils.h"
#include "bindings_generator.h"
#include "csharp_project.h"
#include "dotnet_solution.h"
#include "godotsharp_export.h"
#ifdef OSX_ENABLED
#include "../utils/osx_utils.h"
#endif
#ifdef WINDOWS_ENABLED
#include "../utils/mono_reg_utils.h"
#endif
GodotSharpEditor *GodotSharpEditor::singleton = NULL;
bool GodotSharpEditor::_create_project_solution() {
EditorProgress pr("create_csharp_solution", TTR("Generating solution..."), 2);
pr.step(TTR("Generating C# project..."));
String path = OS::get_singleton()->get_resource_dir();
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
if (appname_safe.empty()) {
appname_safe = "UnnamedProject";
}
String guid = CSharpProject::generate_game_project(path, appname_safe);
if (guid.length()) {
DotNetSolution solution(appname_safe);
if (!solution.set_path(path)) {
show_error_dialog(TTR("Failed to create solution."));
return false;
}
DotNetSolution::ProjectInfo proj_info;
proj_info.guid = guid;
proj_info.relpath = appname_safe + ".csproj";
proj_info.configs.push_back("Debug");
proj_info.configs.push_back("Release");
proj_info.configs.push_back("Tools");
solution.add_new_project(appname_safe, proj_info);
Error sln_error = solution.save();
if (sln_error != OK) {
show_error_dialog(TTR("Failed to save solution."));
return false;
}
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE))
return false;
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR))
return false;
pr.step(TTR("Done"));
// Here, after all calls to progress_task_step
call_deferred("_remove_create_sln_menu_option");
} else {
show_error_dialog(TTR("Failed to create C# project."));
}
return true;
}
void GodotSharpEditor::_make_api_solutions_if_needed() {
// I'm sick entirely of ProgressDialog
static int attempts_left = 100;
if (MessageQueue::get_singleton()->is_flushing() || !SceneTree::get_singleton()) {
ERR_FAIL_COND(attempts_left == 0); // You've got to be kidding
if (SceneTree::get_singleton()) {
SceneTree::get_singleton()->connect("idle_frame", this, "_make_api_solutions_if_needed", Vector<Variant>());
} else {
call_deferred("_make_api_solutions_if_needed");
}
attempts_left--;
return;
}
// Recursion guard needed because signals don't play well with ProgressDialog either, but unlike
// the message queue, with signals the collateral damage should be minimal in the worst case.
static bool recursion_guard = false;
if (!recursion_guard) {
recursion_guard = true;
// Oneshot signals don't play well with ProgressDialog either, so we do it this way instead
SceneTree::get_singleton()->disconnect("idle_frame", this, "_make_api_solutions_if_needed");
_make_api_solutions_if_needed_impl();
recursion_guard = false;
}
}
void GodotSharpEditor::_make_api_solutions_if_needed_impl() {
// If the project has a solution and C# project make sure the API assemblies are present and up to date
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
if (!FileAccess::exists(res_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll")) ||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) {
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE))
return;
}
if (!FileAccess::exists(res_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll")) ||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) {
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR))
return; // Redundant? I don't think so
}
}
void GodotSharpEditor::_remove_create_sln_menu_option() {
menu_popup->remove_item(menu_popup->get_item_index(MENU_CREATE_SLN));
bottom_panel_btn->show();
}
void GodotSharpEditor::_show_about_dialog() {
bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start");
about_dialog_checkbox->set_pressed(show_on_start);
about_dialog->popup_centered_minsize();
}
void GodotSharpEditor::_toggle_about_dialog_on_start(bool p_enabled) {
bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start");
if (show_on_start != p_enabled) {
EditorSettings::get_singleton()->set_setting("mono/editor/show_info_on_start", p_enabled);
}
}
void GodotSharpEditor::_build_solution_pressed() {
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) {
if (!_create_project_solution())
return; // Failed to create solution
}
MonoBottomPanel::get_singleton()->call("_build_project_pressed");
}
void GodotSharpEditor::_menu_option_pressed(int p_id) {
switch (p_id) {
case MENU_CREATE_SLN: {
_create_project_solution();
} break;
case MENU_ABOUT_CSHARP: {
_show_about_dialog();
} break;
default:
ERR_FAIL();
}
}
void GodotSharpEditor::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_READY: {
bool show_info_dialog = EDITOR_GET("mono/editor/show_info_on_start");
if (show_info_dialog) {
about_dialog->set_exclusive(true);
_show_about_dialog();
// Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive then.
about_dialog->set_exclusive(false);
}
}
}
}
void GodotSharpEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_build_solution_pressed"), &GodotSharpEditor::_build_solution_pressed);
ClassDB::bind_method(D_METHOD("_create_project_solution"), &GodotSharpEditor::_create_project_solution);
ClassDB::bind_method(D_METHOD("_make_api_solutions_if_needed"), &GodotSharpEditor::_make_api_solutions_if_needed);
ClassDB::bind_method(D_METHOD("_remove_create_sln_menu_option"), &GodotSharpEditor::_remove_create_sln_menu_option);
ClassDB::bind_method(D_METHOD("_toggle_about_dialog_on_start"), &GodotSharpEditor::_toggle_about_dialog_on_start);
ClassDB::bind_method(D_METHOD("_menu_option_pressed", "id"), &GodotSharpEditor::_menu_option_pressed);
}
MonoBoolean godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled(MonoString *p_bundle_id) {
#ifdef OSX_ENABLED
return (MonoBoolean)osx_is_app_bundle_installed(GDMonoMarshal::mono_string_to_godot(p_bundle_id));
#else
(void)p_bundle_id; // UNUSED
ERR_FAIL_V(false);
#endif
}
MonoString *godot_icall_Utils_OS_GetPlatformName() {
return GDMonoMarshal::mono_string_from_godot(OS::get_singleton()->get_name());
}
void GodotSharpEditor::register_internal_calls() {
static bool registered = false;
ERR_FAIL_COND(registered);
registered = true;
mono_add_internal_call("GodotSharpTools.Editor.MonoDevelopInstance::IsApplicationBundleInstalled", (void *)godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled);
mono_add_internal_call("GodotSharpTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName);
GodotSharpBuilds::register_internal_calls();
GodotSharpExport::register_internal_calls();
}
void GodotSharpEditor::show_error_dialog(const String &p_message, const String &p_title) {
error_dialog->set_title(p_title);
error_dialog->set_text(p_message);
error_dialog->popup_centered_minsize();
}
Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) {
ExternalEditor editor = ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor")));
switch (editor) {
case EDITOR_VSCODE: {
static String vscode_path;
if (vscode_path.empty() || !FileAccess::exists(vscode_path)) {
// Try to search it again if it wasn't found last time or if it was removed from its location
bool found = false;
// TODO: Use initializer lists once C++11 is allowed
static Vector<String> vscode_names;
if (vscode_names.empty()) {
vscode_names.push_back("code");
vscode_names.push_back("code-oss");
vscode_names.push_back("vscode");
vscode_names.push_back("vscode-oss");
vscode_names.push_back("visual-studio-code");
vscode_names.push_back("visual-studio-code-oss");
}
for (int i = 0; i < vscode_names.size(); i++) {
vscode_path = path_which(vscode_names[i]);
if (!vscode_path.empty()) {
found = true;
break;
}
}
if (!found)
vscode_path.clear(); // Not found, clear so next time the empty() check is enough
}
List<String> args;
#ifdef OSX_ENABLED
// The package path is '/Applications/Visual Studio Code.app'
static const String vscode_bundle_id = "com.microsoft.VSCode";
static bool osx_app_bundle_installed = osx_is_app_bundle_installed(vscode_bundle_id);
if (osx_app_bundle_installed) {
args.push_back("-b");
args.push_back(vscode_bundle_id);
// The reusing of existing windows made by the 'open' command might not choose a wubdiw that is
// editing our folder. It's better to ask for a new window and let VSCode do the window management.
args.push_back("-n");
// The open process must wait until the application finishes (which is instant in VSCode's case)
args.push_back("--wait-apps");
args.push_back("--args");
}
#endif
args.push_back(ProjectSettings::get_singleton()->get_resource_path());
String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path());
if (p_line >= 0) {
args.push_back("-g");
args.push_back(script_path + ":" + itos(p_line + 1) + ":" + itos(p_col));
} else {
args.push_back(script_path);
}
#ifdef OSX_ENABLED
ERR_EXPLAIN("Cannot find code editor: VSCode");
ERR_FAIL_COND_V(!osx_app_bundle_installed && vscode_path.empty(), ERR_FILE_NOT_FOUND);
String command = osx_app_bundle_installed ? "/usr/bin/open" : vscode_path;
#else
ERR_EXPLAIN("Cannot find code editor: VSCode");
ERR_FAIL_COND_V(vscode_path.empty(), ERR_FILE_NOT_FOUND);
String command = vscode_path;
#endif
Error err = OS::get_singleton()->execute(command, args, false);
if (err != OK) {
ERR_PRINT("Error when trying to execute code editor: VSCode");
return err;
}
} break;
#ifdef OSX_ENABLED
case EDITOR_VISUALSTUDIO_MAC:
// [[fallthrough]];
#endif
case EDITOR_MONODEVELOP: {
#ifdef OSX_ENABLED
bool is_visualstudio = editor == EDITOR_VISUALSTUDIO_MAC;
MonoDevelopInstance **instance = is_visualstudio ?
&visualstudio_mac_instance :
&monodevelop_instance;
MonoDevelopInstance::EditorId editor_id = is_visualstudio ?
MonoDevelopInstance::VISUALSTUDIO_FOR_MAC :
MonoDevelopInstance::MONODEVELOP;
#else
MonoDevelopInstance **instance = &monodevelop_instance;
MonoDevelopInstance::EditorId editor_id = MonoDevelopInstance::MONODEVELOP;
#endif
if (!*instance)
*instance = memnew(MonoDevelopInstance(GodotSharpDirs::get_project_sln_path(), editor_id));
String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path());
if (p_line >= 0) {
script_path += ";" + itos(p_line + 1) + ";" + itos(p_col);
}
(*instance)->execute(script_path);
} break;
default:
return ERR_UNAVAILABLE;
}
return OK;
}
bool GodotSharpEditor::overrides_external_editor() {
return ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))) != EDITOR_NONE;
}
GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) {
singleton = this;
monodevelop_instance = NULL;
#ifdef OSX_ENABLED
visualstudio_mac_instance = NULL;
#endif
editor = p_editor;
error_dialog = memnew(AcceptDialog);
editor->get_gui_base()->add_child(error_dialog);
bottom_panel_btn = editor->add_bottom_panel_item(TTR("Mono"), memnew(MonoBottomPanel(editor)));
godotsharp_builds = memnew(GodotSharpBuilds);
editor->add_child(memnew(MonoReloadNode));
menu_popup = memnew(PopupMenu);
menu_popup->hide();
menu_popup->set_as_toplevel(true);
menu_popup->set_pass_on_modal_close_click(false);
editor->add_tool_submenu_item("Mono", menu_popup);
// TODO: Remove or edit this info dialog once Mono support is no longer in alpha
{
menu_popup->add_item(TTR("About C# support"), MENU_ABOUT_CSHARP);
about_dialog = memnew(AcceptDialog);
editor->get_gui_base()->add_child(about_dialog);
about_dialog->set_title("Important: C# support is not feature-complete");
// We don't use set_text() as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox
// we'll add. Instead we add containers and a new autowrapped Label inside.
// Main VBoxContainer (icon + label on top, checkbox at bottom)
VBoxContainer *about_vbc = memnew(VBoxContainer);
about_dialog->add_child(about_vbc);
// HBoxContainer for icon + label
HBoxContainer *about_hbc = memnew(HBoxContainer);
about_vbc->add_child(about_hbc);
TextureRect *about_icon = memnew(TextureRect);
about_hbc->add_child(about_icon);
Ref<Texture> about_icon_tex = about_icon->get_icon("NodeWarning", "EditorIcons");
about_icon->set_texture(about_icon_tex);
Label *about_label = memnew(Label);
about_hbc->add_child(about_label);
about_label->set_custom_minimum_size(Size2(600, 150) * EDSCALE);
about_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
about_label->set_autowrap(true);
String about_text =
String("C# support in Godot Engine is in late alpha stage and, while already usable, ") +
"it is not meant for use in production.\n\n" +
"Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " +
"Bugs and usability issues will be addressed gradually over future releases, " +
"potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" +
"If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" +
" https://github.com/godotengine/godot/issues\n\n" +
"Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!";
about_label->set_text(about_text);
EDITOR_DEF("mono/editor/show_info_on_start", true);
// CheckBox in main container
about_dialog_checkbox = memnew(CheckBox);
about_vbc->add_child(about_dialog_checkbox);
about_dialog_checkbox->set_text("Show this warning when starting the editor");
about_dialog_checkbox->connect("toggled", this, "_toggle_about_dialog_on_start");
}
String sln_path = GodotSharpDirs::get_project_sln_path();
String csproj_path = GodotSharpDirs::get_project_csproj_path();
if (FileAccess::exists(sln_path) && FileAccess::exists(csproj_path)) {
// Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized.
call_deferred("_make_api_solutions_if_needed");
} else {
bottom_panel_btn->hide();
menu_popup->add_item(TTR("Create C# solution"), MENU_CREATE_SLN);
}
menu_popup->connect("id_pressed", this, "_menu_option_pressed");
ToolButton *build_button = memnew(ToolButton);
build_button->set_text("Build");
build_button->set_tooltip("Build solution");
build_button->set_focus_mode(Control::FOCUS_NONE);
build_button->connect("pressed", this, "_build_solution_pressed");
editor->get_menu_hb()->add_child(build_button);
// External editor settings
EditorSettings *ed_settings = EditorSettings::get_singleton();
EDITOR_DEF("mono/editor/external_editor", EDITOR_NONE);
String settings_hint_str = "Disabled";
#if defined(WINDOWS_ENABLED)
settings_hint_str += ",MonoDevelop,Visual Studio Code";
#elif defined(OSX_ENABLED)
settings_hint_str += ",Visual Studio,MonoDevelop,Visual Studio Code";
#elif defined(UNIX_ENABLED)
settings_hint_str += ",MonoDevelop,Visual Studio Code";
#endif
ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/editor/external_editor", PROPERTY_HINT_ENUM, settings_hint_str));
// Export plugin
Ref<GodotSharpExport> godotsharp_export;
godotsharp_export.instance();
EditorExport::get_singleton()->add_export_plugin(godotsharp_export);
}
GodotSharpEditor::~GodotSharpEditor() {
singleton = NULL;
memdelete(godotsharp_builds);
if (monodevelop_instance) {
memdelete(monodevelop_instance);
monodevelop_instance = NULL;
}
}
MonoReloadNode *MonoReloadNode::singleton = NULL;
void MonoReloadNode::_reload_timer_timeout() {
if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
CSharpLanguage::get_singleton()->reload_assemblies(false);
}
}
void MonoReloadNode::restart_reload_timer() {
reload_timer->stop();
reload_timer->start();
}
void MonoReloadNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("_reload_timer_timeout"), &MonoReloadNode::_reload_timer_timeout);
}
void MonoReloadNode::_notification(int p_what) {
switch (p_what) {
case MainLoop::NOTIFICATION_WM_FOCUS_IN: {
restart_reload_timer();
if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
CSharpLanguage::get_singleton()->reload_assemblies(false);
}
} break;
default: {
} break;
};
}
MonoReloadNode::MonoReloadNode() {
singleton = this;
reload_timer = memnew(Timer);
add_child(reload_timer);
reload_timer->set_one_shot(false);
reload_timer->set_wait_time(EDITOR_DEF("mono/assembly_watch_interval_sec", 0.5));
reload_timer->connect("timeout", this, "_reload_timer_timeout");
reload_timer->start();
}
MonoReloadNode::~MonoReloadNode() {
singleton = NULL;
}

View file

@ -1,134 +0,0 @@
/*************************************************************************/
/* godotsharp_editor.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 GODOTSHARP_EDITOR_H
#define GODOTSHARP_EDITOR_H
#include "godotsharp_builds.h"
#include "monodevelop_instance.h"
class GodotSharpEditor : public Node {
GDCLASS(GodotSharpEditor, Node);
EditorNode *editor;
MenuButton *menu_button;
PopupMenu *menu_popup;
AcceptDialog *error_dialog;
AcceptDialog *about_dialog;
CheckBox *about_dialog_checkbox;
ToolButton *bottom_panel_btn;
GodotSharpBuilds *godotsharp_builds;
MonoDevelopInstance *monodevelop_instance;
#ifdef OSX_ENABLED
MonoDevelopInstance *visualstudio_mac_instance;
#endif
bool _create_project_solution();
void _make_api_solutions_if_needed();
void _make_api_solutions_if_needed_impl();
void _remove_create_sln_menu_option();
void _show_about_dialog();
void _toggle_about_dialog_on_start(bool p_enabled);
void _menu_option_pressed(int p_id);
void _build_solution_pressed();
static GodotSharpEditor *singleton;
protected:
void _notification(int p_notification);
static void _bind_methods();
public:
enum MenuOptions {
MENU_CREATE_SLN,
MENU_ABOUT_CSHARP,
};
enum ExternalEditor {
EDITOR_NONE,
#if defined(WINDOWS_ENABLED)
//EDITOR_VISUALSTUDIO, // TODO
EDITOR_MONODEVELOP,
EDITOR_VSCODE
#elif defined(OSX_ENABLED)
EDITOR_VISUALSTUDIO_MAC,
EDITOR_MONODEVELOP,
EDITOR_VSCODE
#elif defined(UNIX_ENABLED)
EDITOR_MONODEVELOP,
EDITOR_VSCODE
#endif
};
_FORCE_INLINE_ static GodotSharpEditor *get_singleton() { return singleton; }
static void register_internal_calls();
void show_error_dialog(const String &p_message, const String &p_title = "Error");
Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col);
bool overrides_external_editor();
GodotSharpEditor(EditorNode *p_editor);
~GodotSharpEditor();
};
class MonoReloadNode : public Node {
GDCLASS(MonoReloadNode, Node);
Timer *reload_timer;
void _reload_timer_timeout();
static MonoReloadNode *singleton;
protected:
static void _bind_methods();
void _notification(int p_what);
public:
_FORCE_INLINE_ static MonoReloadNode *get_singleton() { return singleton; }
void restart_reload_timer();
MonoReloadNode();
~MonoReloadNode();
};
#endif // GODOTSHARP_EDITOR_H

View file

@ -30,180 +30,28 @@
#include "godotsharp_export.h"
#include "core/version.h"
#include <mono/metadata/image.h>
#include "../csharp_script.h"
#include "../godotsharp_defs.h"
#include "../godotsharp_dirs.h"
#include "../mono_gd/gd_mono_class.h"
#include "../mono_gd/gd_mono_marshal.h"
#include "csharp_project.h"
#include "godotsharp_builds.h"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_assembly.h"
static MonoString *godot_icall_GodotSharpExport_GetTemplatesDir() {
String current_version = VERSION_FULL_CONFIG;
String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(current_version);
return GDMonoMarshal::mono_string_from_godot(ProjectSettings::get_singleton()->globalize_path(templates_dir));
String get_assemblyref_name(MonoImage *p_image, int index) {
const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
uint32_t cols[MONO_ASSEMBLYREF_SIZE];
mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE);
return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
}
static MonoString *godot_icall_GodotSharpExport_GetDataDirName() {
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
return GDMonoMarshal::mono_string_from_godot("data_" + appname_safe);
}
void GodotSharpExport::register_internal_calls() {
static bool registered = false;
ERR_FAIL_COND(registered);
registered = true;
mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetTemplatesDir", (void *)godot_icall_GodotSharpExport_GetTemplatesDir);
mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetDataDirName", (void *)godot_icall_GodotSharpExport_GetDataDirName);
}
void GodotSharpExport::_export_file(const String &p_path, const String &p_type, const Set<String> &) {
if (p_type != CSharpLanguage::get_singleton()->get_type())
return;
ERR_FAIL_COND(p_path.get_extension() != CSharpLanguage::get_singleton()->get_extension());
// TODO what if the source file is not part of the game's C# project
if (!GLOBAL_GET("mono/export/include_scripts_content")) {
// We don't want to include the source code on exported games
add_file(p_path, Vector<uint8_t>(), false);
skip();
}
}
void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags) {
// TODO right now there is no way to stop the export process with an error
ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized());
ERR_FAIL_NULL(TOOLS_DOMAIN);
ERR_FAIL_NULL(GDMono::get_singleton()->get_editor_tools_assembly());
if (FileAccess::exists(GodotSharpDirs::get_project_sln_path())) {
String build_config = p_debug ? "Debug" : "Release";
String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata." + String(p_debug ? "debug" : "release"));
Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path);
ERR_FAIL_COND(metadata_err != OK);
ERR_FAIL_COND(!_add_file(scripts_metadata_path, scripts_metadata_path));
// Turn export features into defines
Vector<String> godot_defines;
for (Set<String>::Element *E = p_features.front(); E; E = E->next()) {
godot_defines.push_back(E->get());
}
ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config, godot_defines));
// Add dependency assemblies
Map<String, String> dependencies;
String project_dll_name = ProjectSettings::get_singleton()->get("application/config/name");
String project_dll_name_safe = OS::get_singleton()->get_safe_dir_name(project_dll_name);
if (project_dll_name_safe.empty()) {
project_dll_name_safe = "UnnamedProject";
}
String project_dll_src_dir = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config);
String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name_safe + ".dll");
dependencies.insert(project_dll_name_safe, project_dll_src_path);
{
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
ERR_FAIL_NULL(export_domain);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
_GDMONO_SCOPE_DOMAIN_(export_domain);
GDMonoAssembly *scripts_assembly = NULL;
bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name_safe,
project_dll_src_path, &scripts_assembly, /* refonly: */ true);
ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name_safe);
ERR_FAIL_COND(!load_success);
Vector<String> search_dirs;
String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);
String android_bcl_dir = templates_dir.plus_file("android-bcl");
String custom_lib_dir;
if (p_features.find("Android") && DirAccess::exists(android_bcl_dir)) {
custom_lib_dir = android_bcl_dir;
}
GDMonoAssembly::fill_search_dirs(search_dirs, build_config, custom_lib_dir);
Error depend_error = _get_assembly_dependencies(scripts_assembly, search_dirs, dependencies);
ERR_FAIL_COND(depend_error != OK);
}
for (Map<String, String>::Element *E = dependencies.front(); E; E = E->next()) {
String depend_src_path = E->value();
String depend_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(depend_src_path.get_file());
ERR_FAIL_COND(!_add_file(depend_src_path, depend_dst_path));
}
}
// Mono specific export template extras (data dir)
GDMonoClass *export_class = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "GodotSharpExport");
ERR_FAIL_NULL(export_class);
GDMonoMethod *export_begin_method = export_class->get_method("_ExportBegin", 4);
ERR_FAIL_NULL(export_begin_method);
MonoArray *features = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_features.size());
int i = 0;
for (const Set<String>::Element *E = p_features.front(); E; E = E->next()) {
MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E->get());
mono_array_setref(features, i, boxed);
i++;
}
MonoBoolean debug = p_debug;
MonoString *path = GDMonoMarshal::mono_string_from_godot(p_path);
uint32_t flags = p_flags;
void *args[4] = { features, &debug, path, &flags };
MonoException *exc = NULL;
export_begin_method->invoke_raw(NULL, args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL();
}
}
bool GodotSharpExport::_add_file(const String &p_src_path, const String &p_dst_path, bool p_remap) {
FileAccessRef f = FileAccess::open(p_src_path, FileAccess::READ);
ERR_FAIL_COND_V(!f, false);
Vector<uint8_t> data;
data.resize(f->get_len());
f->get_buffer(data.ptrw(), data.size());
add_file(p_dst_path, data, p_remap);
return true;
}
Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies) {
Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
MonoAssemblyName *ref_aname = aname_prealloc;
mono_assembly_get_assemblyref(image, i, ref_aname);
String ref_name = mono_assembly_name_get_name(ref_aname);
String ref_name = get_assemblyref_name(image, i);
if (r_dependencies.find(ref_name))
if (r_dependencies.has(ref_name))
continue;
GDMonoAssembly *ref_assembly = NULL;
@ -242,9 +90,9 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c
ERR_FAIL_V(ERR_CANT_RESOLVE);
}
r_dependencies.insert(ref_name, ref_assembly->get_path());
r_dependencies[ref_name] = ref_assembly->get_path();
Error err = _get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies);
Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies);
if (err != OK) {
ERR_EXPLAIN("Cannot load one of the dependencies for the assembly: " + ref_name);
ERR_FAIL_V(err);
@ -254,14 +102,22 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c
return OK;
}
GodotSharpExport::GodotSharpExport() {
// MonoAssemblyName is an incomplete type (internal to mono), so we can't allocate it ourselves.
// There isn't any api to allocate an empty one either, so we need to do it this way.
aname_prealloc = mono_assembly_name_new("whatever");
mono_assembly_name_free(aname_prealloc); // "it does not frees the object itself, only the name members" (typo included)
}
Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies) {
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
ERR_FAIL_NULL_V(export_domain, FAILED);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
GodotSharpExport::~GodotSharpExport() {
if (aname_prealloc)
mono_free(aname_prealloc);
_GDMONO_SCOPE_DOMAIN_(export_domain);
GDMonoAssembly *scripts_assembly = NULL;
bool load_success = GDMono::get_singleton()->load_assembly_from(p_project_dll_name,
p_project_dll_src_path, &scripts_assembly, /* refonly: */ true);
ERR_EXPLAIN("Cannot load assembly (refonly): " + p_project_dll_name);
ERR_FAIL_COND_V(!load_success, ERR_CANT_RESOLVE);
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir);
return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies);
}

View file

@ -31,29 +31,19 @@
#ifndef GODOTSHARP_EXPORT_H
#define GODOTSHARP_EXPORT_H
#include <mono/metadata/image.h>
#include "editor/editor_export.h"
#include "core/dictionary.h"
#include "core/error_list.h"
#include "core/ustring.h"
#include "../mono_gd/gd_mono_header.h"
class GodotSharpExport : public EditorExportPlugin {
namespace GodotSharpExport {
MonoAssemblyName *aname_prealloc;
Error get_exported_assembly_dependencies(const String &p_project_dll_name,
const String &p_project_dll_src_path, const String &p_build_config,
const String &p_custom_lib_dir, Dictionary &r_dependencies);
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
bool _add_file(const String &p_src_path, const String &p_dst_path, bool p_remap = false);
Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies);
protected:
virtual void _export_file(const String &p_path, const String &p_type, const Set<String> &p_features);
virtual void _export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags);
public:
static void register_internal_calls();
GodotSharpExport();
~GodotSharpExport();
};
} // namespace GodotSharpExport
#endif // GODOTSHARP_EXPORT_H

View file

@ -1,528 +0,0 @@
/*************************************************************************/
/* mono_bottom_panel.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "mono_bottom_panel.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/script_editor_debugger.h"
#include "../csharp_script.h"
#include "../godotsharp_dirs.h"
#include "csharp_project.h"
#include "godotsharp_editor.h"
MonoBottomPanel *MonoBottomPanel::singleton = NULL;
void MonoBottomPanel::_update_build_tabs_list() {
build_tabs_list->clear();
int current_tab = build_tabs->get_current_tab();
bool no_current_tab = current_tab < 0 || current_tab >= build_tabs->get_tab_count();
for (int i = 0; i < build_tabs->get_child_count(); i++) {
MonoBuildTab *tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(i));
if (tab) {
String item_name = tab->build_info.solution.get_file().get_basename();
item_name += " [" + tab->build_info.configuration + "]";
build_tabs_list->add_item(item_name, tab->get_icon_texture());
String item_tooltip = "Solution: " + tab->build_info.solution;
item_tooltip += "\nConfiguration: " + tab->build_info.configuration;
item_tooltip += "\nStatus: ";
if (tab->build_exited) {
item_tooltip += tab->build_result == MonoBuildTab::RESULT_SUCCESS ? "Succeeded" : "Errored";
} else {
item_tooltip += "Running";
}
if (!tab->build_exited || tab->build_result == MonoBuildTab::RESULT_ERROR) {
item_tooltip += "\nErrors: " + itos(tab->error_count);
}
item_tooltip += "\nWarnings: " + itos(tab->warning_count);
build_tabs_list->set_item_tooltip(i, item_tooltip);
if (no_current_tab || current_tab == i) {
build_tabs_list->select(i);
_build_tabs_item_selected(i);
}
}
}
}
void MonoBottomPanel::add_build_tab(MonoBuildTab *p_build_tab) {
build_tabs->add_child(p_build_tab);
raise_build_tab(p_build_tab);
}
void MonoBottomPanel::raise_build_tab(MonoBuildTab *p_build_tab) {
ERR_FAIL_COND(p_build_tab->get_parent() != build_tabs);
build_tabs->move_child(p_build_tab, 0);
_update_build_tabs_list();
}
void MonoBottomPanel::show_build_tab() {
for (int i = 0; i < panel_tabs->get_tab_count(); i++) {
if (panel_tabs->get_tab_control(i) == panel_builds_tab) {
panel_tabs->set_current_tab(i);
editor->make_bottom_panel_item_visible(this);
return;
}
}
ERR_PRINT("Builds tab not found");
}
void MonoBottomPanel::_build_tabs_item_selected(int p_idx) {
ERR_FAIL_INDEX(p_idx, build_tabs->get_tab_count());
build_tabs->set_current_tab(p_idx);
if (!build_tabs->is_visible())
build_tabs->set_visible(true);
warnings_btn->set_visible(true);
errors_btn->set_visible(true);
view_log_btn->set_visible(true);
}
void MonoBottomPanel::_build_tabs_nothing_selected() {
if (build_tabs->get_tab_count() != 0) { // just in case
build_tabs->set_visible(false);
// This callback is called when clicking on the empty space of the list.
// ItemList won't deselect the items automatically, so we must do it ourselves.
build_tabs_list->unselect_all();
}
warnings_btn->set_visible(false);
errors_btn->set_visible(false);
view_log_btn->set_visible(false);
}
void MonoBottomPanel::_warnings_toggled(bool p_pressed) {
int current_tab = build_tabs->get_current_tab();
ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count());
MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab));
build_tab->warnings_visible = p_pressed;
build_tab->_update_issues_list();
}
void MonoBottomPanel::_errors_toggled(bool p_pressed) {
int current_tab = build_tabs->get_current_tab();
ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count());
MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab));
build_tab->errors_visible = p_pressed;
build_tab->_update_issues_list();
}
void MonoBottomPanel::_build_project_pressed() {
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
return; // No solution to build
String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor");
String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player");
Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor);
ERR_FAIL_COND(metadata_err != OK);
if (FileAccess::exists(scripts_metadata_path_editor)) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player);
ERR_EXPLAIN("Failed to copy scripts metadata file");
ERR_FAIL_COND(copy_err != OK);
}
Vector<String> godot_defines;
godot_defines.push_back(OS::get_singleton()->get_name());
godot_defines.push_back((sizeof(void *) == 4 ? "32" : "64"));
bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools", godot_defines);
if (build_success) {
// Notify running game for hot-reload
ScriptEditor::get_singleton()->get_debugger()->reload_scripts();
// Hot-reload in the editor
MonoReloadNode::get_singleton()->restart_reload_timer();
if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
CSharpLanguage::get_singleton()->reload_assemblies(false);
}
}
}
void MonoBottomPanel::_view_log_pressed() {
if (build_tabs_list->is_anything_selected()) {
Vector<int> selected_items = build_tabs_list->get_selected_items();
CRASH_COND(selected_items.size() != 1);
int selected_item = selected_items[0];
MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_tab_control(selected_item));
ERR_FAIL_NULL(build_tab);
String log_dirpath = build_tab->get_build_info().get_log_dirpath();
OS::get_singleton()->shell_open(log_dirpath.plus_file(GodotSharpBuilds::get_msbuild_log_filename()));
}
}
void MonoBottomPanel::_notification(int p_what) {
switch (p_what) {
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles"));
panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles"));
panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles"));
} break;
}
}
void MonoBottomPanel::_bind_methods() {
ClassDB::bind_method(D_METHOD("_build_project_pressed"), &MonoBottomPanel::_build_project_pressed);
ClassDB::bind_method(D_METHOD("_view_log_pressed"), &MonoBottomPanel::_view_log_pressed);
ClassDB::bind_method(D_METHOD("_warnings_toggled", "pressed"), &MonoBottomPanel::_warnings_toggled);
ClassDB::bind_method(D_METHOD("_errors_toggled", "pressed"), &MonoBottomPanel::_errors_toggled);
ClassDB::bind_method(D_METHOD("_build_tabs_item_selected", "idx"), &MonoBottomPanel::_build_tabs_item_selected);
ClassDB::bind_method(D_METHOD("_build_tabs_nothing_selected"), &MonoBottomPanel::_build_tabs_nothing_selected);
}
MonoBottomPanel::MonoBottomPanel(EditorNode *p_editor) {
singleton = this;
editor = p_editor;
set_v_size_flags(SIZE_EXPAND_FILL);
set_anchors_and_margins_preset(Control::PRESET_WIDE);
panel_tabs = memnew(TabContainer);
panel_tabs->set_tab_align(TabContainer::ALIGN_LEFT);
panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles"));
panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles"));
panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles"));
panel_tabs->set_custom_minimum_size(Size2(0, 228) * EDSCALE);
panel_tabs->set_v_size_flags(SIZE_EXPAND_FILL);
add_child(panel_tabs);
{ // Builds
panel_builds_tab = memnew(VBoxContainer);
panel_builds_tab->set_name(TTR("Builds"));
panel_builds_tab->set_h_size_flags(SIZE_EXPAND_FILL);
panel_tabs->add_child(panel_builds_tab);
HBoxContainer *toolbar_hbc = memnew(HBoxContainer);
toolbar_hbc->set_h_size_flags(SIZE_EXPAND_FILL);
panel_builds_tab->add_child(toolbar_hbc);
Button *build_project_btn = memnew(Button);
build_project_btn->set_text(TTR("Build Project"));
build_project_btn->set_focus_mode(FOCUS_NONE);
build_project_btn->connect("pressed", this, "_build_project_pressed");
toolbar_hbc->add_child(build_project_btn);
toolbar_hbc->add_spacer();
warnings_btn = memnew(ToolButton);
warnings_btn->set_text(TTR("Warnings"));
warnings_btn->set_toggle_mode(true);
warnings_btn->set_pressed(true);
warnings_btn->set_visible(false);
warnings_btn->set_focus_mode(FOCUS_NONE);
warnings_btn->connect("toggled", this, "_warnings_toggled");
toolbar_hbc->add_child(warnings_btn);
errors_btn = memnew(ToolButton);
errors_btn->set_text(TTR("Errors"));
errors_btn->set_toggle_mode(true);
errors_btn->set_pressed(true);
errors_btn->set_visible(false);
errors_btn->set_focus_mode(FOCUS_NONE);
errors_btn->connect("toggled", this, "_errors_toggled");
toolbar_hbc->add_child(errors_btn);
toolbar_hbc->add_spacer();
view_log_btn = memnew(Button);
view_log_btn->set_text(TTR("View log"));
view_log_btn->set_focus_mode(FOCUS_NONE);
view_log_btn->set_visible(false);
view_log_btn->connect("pressed", this, "_view_log_pressed");
toolbar_hbc->add_child(view_log_btn);
HSplitContainer *hsc = memnew(HSplitContainer);
hsc->set_h_size_flags(SIZE_EXPAND_FILL);
hsc->set_v_size_flags(SIZE_EXPAND_FILL);
panel_builds_tab->add_child(hsc);
build_tabs_list = memnew(ItemList);
build_tabs_list->set_h_size_flags(SIZE_EXPAND_FILL);
build_tabs_list->connect("item_selected", this, "_build_tabs_item_selected");
build_tabs_list->connect("nothing_selected", this, "_build_tabs_nothing_selected");
hsc->add_child(build_tabs_list);
build_tabs = memnew(TabContainer);
build_tabs->set_tab_align(TabContainer::ALIGN_LEFT);
build_tabs->set_h_size_flags(SIZE_EXPAND_FILL);
build_tabs->set_tabs_visible(false);
hsc->add_child(build_tabs);
}
}
MonoBottomPanel::~MonoBottomPanel() {
singleton = NULL;
}
void MonoBuildTab::_load_issues_from_file(const String &p_csv_file) {
FileAccessRef f = FileAccess::open(p_csv_file, FileAccess::READ);
if (!f)
return;
while (!f->eof_reached()) {
Vector<String> csv_line = f->get_csv_line();
if (csv_line.size() == 1 && csv_line[0].empty())
return;
ERR_CONTINUE(csv_line.size() != 7);
BuildIssue issue;
issue.warning = csv_line[0] == "warning";
issue.file = csv_line[1];
issue.line = csv_line[2].to_int();
issue.column = csv_line[3].to_int();
issue.code = csv_line[4];
issue.message = csv_line[5];
issue.project_file = csv_line[6];
if (issue.warning)
warning_count += 1;
else
error_count += 1;
issues.push_back(issue);
}
}
void MonoBuildTab::_update_issues_list() {
issues_list->clear();
Ref<Texture> warning_icon = get_icon("Warning", "EditorIcons");
Ref<Texture> error_icon = get_icon("Error", "EditorIcons");
for (int i = 0; i < issues.size(); i++) {
const BuildIssue &issue = issues[i];
if (!(issue.warning ? warnings_visible : errors_visible))
continue;
String tooltip;
tooltip += String("Message: ") + issue.message;
if (issue.code.length()) {
tooltip += String("\nCode: ") + issue.code;
}
tooltip += String("\nType: ") + (issue.warning ? "warning" : "error");
String text;
if (issue.file.length()) {
String sline = String::num_int64(issue.line);
String scolumn = String::num_int64(issue.column);
text += issue.file + "(";
text += sline + ",";
text += scolumn + "): ";
tooltip += "\nFile: " + issue.file;
tooltip += "\nLine: " + sline;
tooltip += "\nColumn: " + scolumn;
}
if (issue.project_file.length()) {
tooltip += "\nProject: " + issue.project_file;
}
text += issue.message;
int line_break_idx = text.find("\n");
issues_list->add_item(line_break_idx == -1 ? text : text.substr(0, line_break_idx),
issue.warning ? warning_icon : error_icon);
int index = issues_list->get_item_count() - 1;
issues_list->set_item_tooltip(index, tooltip);
issues_list->set_item_metadata(index, i);
}
}
Ref<Texture> MonoBuildTab::get_icon_texture() const {
if (build_exited) {
if (build_result == RESULT_ERROR) {
return get_icon("StatusError", "EditorIcons");
} else {
return get_icon("StatusSuccess", "EditorIcons");
}
} else {
return get_icon("Stop", "EditorIcons");
}
}
MonoBuildInfo MonoBuildTab::get_build_info() {
return build_info;
}
void MonoBuildTab::on_build_start() {
build_exited = false;
issues.clear();
warning_count = 0;
error_count = 0;
_update_issues_list();
MonoBottomPanel::get_singleton()->raise_build_tab(this);
}
void MonoBuildTab::on_build_exit(BuildResult result) {
build_exited = true;
build_result = result;
_load_issues_from_file(logs_dir.plus_file(GodotSharpBuilds::get_msbuild_issues_filename()));
_update_issues_list();
MonoBottomPanel::get_singleton()->raise_build_tab(this);
}
void MonoBuildTab::on_build_exec_failed(const String &p_cause) {
build_exited = true;
build_result = RESULT_ERROR;
issues_list->clear();
BuildIssue issue;
issue.message = p_cause;
issue.warning = false;
error_count += 1;
issues.push_back(issue);
_update_issues_list();
MonoBottomPanel::get_singleton()->raise_build_tab(this);
}
void MonoBuildTab::restart_build() {
ERR_FAIL_COND(!build_exited);
GodotSharpBuilds::get_singleton()->restart_build(this);
}
void MonoBuildTab::stop_build() {
ERR_FAIL_COND(build_exited);
GodotSharpBuilds::get_singleton()->stop_build(this);
}
void MonoBuildTab::_issue_activated(int p_idx) {
ERR_FAIL_INDEX(p_idx, issues_list->get_item_count());
// Get correct issue idx from issue list
int issue_idx = this->issues_list->get_item_metadata(p_idx);
ERR_FAIL_INDEX(issue_idx, issues.size());
const BuildIssue &issue = issues[issue_idx];
if (issue.project_file.empty() && issue.file.empty())
return;
String project_dir = issue.project_file.length() ? issue.project_file.get_base_dir() : build_info.solution.get_base_dir();
String file = project_dir.simplify_path().plus_file(issue.file.simplify_path());
if (!FileAccess::exists(file))
return;
file = ProjectSettings::get_singleton()->localize_path(file);
if (file.begins_with("res://")) {
Ref<Script> script = ResourceLoader::load(file, CSharpLanguage::get_singleton()->get_type());
if (script.is_valid() && ScriptEditor::get_singleton()->edit(script, issue.line, issue.column)) {
EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT);
}
}
}
void MonoBuildTab::_bind_methods() {
ClassDB::bind_method("_issue_activated", &MonoBuildTab::_issue_activated);
}
MonoBuildTab::MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir) :
build_exited(false),
issues_list(memnew(ItemList)),
error_count(0),
warning_count(0),
errors_visible(true),
warnings_visible(true),
logs_dir(p_logs_dir),
build_info(p_build_info) {
issues_list->set_v_size_flags(SIZE_EXPAND_FILL);
issues_list->connect("item_activated", this, "_issue_activated");
add_child(issues_list);
}

View file

@ -1,150 +0,0 @@
/*************************************************************************/
/* mono_bottom_panel.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 MONO_BOTTOM_PANEL_H
#define MONO_BOTTOM_PANEL_H
#include "editor/editor_node.h"
#include "scene/gui/control.h"
#include "mono_build_info.h"
class MonoBuildTab;
class MonoBottomPanel : public VBoxContainer {
GDCLASS(MonoBottomPanel, VBoxContainer);
EditorNode *editor;
TabContainer *panel_tabs;
VBoxContainer *panel_builds_tab;
ItemList *build_tabs_list;
TabContainer *build_tabs;
ToolButton *warnings_btn;
ToolButton *errors_btn;
Button *view_log_btn;
void _update_build_tabs_list();
void _build_tabs_item_selected(int p_idx);
void _build_tabs_nothing_selected();
void _warnings_toggled(bool p_pressed);
void _errors_toggled(bool p_pressed);
void _build_project_pressed();
void _view_log_pressed();
static MonoBottomPanel *singleton;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
_FORCE_INLINE_ static MonoBottomPanel *get_singleton() { return singleton; }
void add_build_tab(MonoBuildTab *p_build_tab);
void raise_build_tab(MonoBuildTab *p_build_tab);
void show_build_tab();
MonoBottomPanel(EditorNode *p_editor = NULL);
~MonoBottomPanel();
};
class MonoBuildTab : public VBoxContainer {
GDCLASS(MonoBuildTab, VBoxContainer);
public:
enum BuildResult {
RESULT_ERROR,
RESULT_SUCCESS
};
struct BuildIssue {
bool warning;
String file;
int line;
int column;
String code;
String message;
String project_file;
};
private:
friend class MonoBottomPanel;
bool build_exited;
BuildResult build_result;
Vector<BuildIssue> issues;
ItemList *issues_list;
int error_count;
int warning_count;
bool errors_visible;
bool warnings_visible;
String logs_dir;
MonoBuildInfo build_info;
void _load_issues_from_file(const String &p_csv_file);
void _update_issues_list();
void _issue_activated(int p_idx);
protected:
static void _bind_methods();
public:
Ref<Texture> get_icon_texture() const;
MonoBuildInfo get_build_info();
void on_build_start();
void on_build_exit(BuildResult result);
void on_build_exec_failed(const String &p_cause);
void restart_build();
void stop_build();
MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir);
};
#endif // MONO_BOTTOM_PANEL_H

View file

@ -1,62 +0,0 @@
/*************************************************************************/
/* mono_build_info.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "mono_build_info.h"
#include "../godotsharp_dirs.h"
#include "../mono_gd/gd_mono_utils.h"
uint32_t MonoBuildInfo::Hasher::hash(const MonoBuildInfo &p_key) {
uint32_t hash = 0;
GDMonoUtils::hash_combine(hash, p_key.solution.hash());
GDMonoUtils::hash_combine(hash, p_key.configuration.hash());
return hash;
}
bool MonoBuildInfo::operator==(const MonoBuildInfo &p_b) const {
return p_b.solution == solution && p_b.configuration == configuration;
}
String MonoBuildInfo::get_log_dirpath() {
return GodotSharpDirs::get_build_logs_dir().plus_file(solution.md5_text() + "_" + configuration);
}
MonoBuildInfo::MonoBuildInfo() {}
MonoBuildInfo::MonoBuildInfo(const String &p_solution, const String &p_config) {
solution = p_solution;
configuration = p_config;
}

View file

@ -1,85 +0,0 @@
/*************************************************************************/
/* monodevelop_instance.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "monodevelop_instance.h"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_class.h"
void MonoDevelopInstance::execute(const Vector<String> &p_files) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
ERR_FAIL_NULL(execute_method);
ERR_FAIL_COND(gc_handle.is_null());
MonoException *exc = NULL;
Variant files = p_files;
const Variant *args[1] = { &files };
execute_method->invoke(gc_handle->get_target(), args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL();
}
}
void MonoDevelopInstance::execute(const String &p_file) {
Vector<String> files;
files.push_back(p_file);
execute(files);
}
MonoDevelopInstance::MonoDevelopInstance(const String &p_solution, EditorId p_editor_id) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "MonoDevelopInstance");
MonoObject *obj = mono_object_new(TOOLS_DOMAIN, klass->get_mono_ptr());
GDMonoMethod *ctor = klass->get_method(".ctor", 2);
MonoException *exc = NULL;
Variant solution = p_solution;
Variant editor_id = p_editor_id;
const Variant *args[2] = { &solution, &editor_id };
ctor->invoke(obj, args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL();
}
gc_handle = MonoGCHandle::create_strong(obj);
execute_method = klass->get_method("Execute", 1);
}

View file

@ -1,56 +0,0 @@
/*************************************************************************/
/* monodevelop_instance.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 MONODEVELOP_INSTANCE_H
#define MONODEVELOP_INSTANCE_H
#include "core/reference.h"
#include "../mono_gc_handle.h"
#include "../mono_gd/gd_mono_method.h"
class MonoDevelopInstance {
Ref<MonoGCHandle> gc_handle;
GDMonoMethod *execute_method;
public:
enum EditorId {
MONODEVELOP = 0,
VISUALSTUDIO_FOR_MAC = 1
};
void execute(const Vector<String> &p_files);
void execute(const String &p_file);
MonoDevelopInstance(const String &p_solution, EditorId p_editor_id);
};
#endif // MONODEVELOP_INSTANCE_H

2
modules/mono/glue/Managed/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Generated Godot API solution folder
Generated

View file

@ -2,27 +2,27 @@ using System;
namespace Godot
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class RemoteAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class SyncAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class MasterAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class PuppetAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class SlaveAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class RemoteSyncAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class MasterSyncAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class PuppetSyncAttribute : Attribute {}
}

View file

@ -0,0 +1,8 @@
namespace Godot
{
public interface ISerializationListener
{
void OnBeforeSerialize();
void OnAfterDeserialize();
}
}

View file

@ -299,14 +299,14 @@ namespace Godot
if (basepos != -1)
{
var end = basepos + 3;
rs = instance.Substring(end, instance.Length);
rs = instance.Substring(end);
@base = instance.Substring(0, end);
}
else
{
if (instance.BeginsWith("/"))
{
rs = instance.Substring(1, instance.Length);
rs = instance.Substring(1);
@base = "/";
}
else
@ -333,7 +333,7 @@ namespace Godot
if (sep == -1)
return instance;
return instance.Substring(sep + 1, instance.Length);
return instance.Substring(sep + 1);
}
// <summary>
@ -911,7 +911,8 @@ namespace Godot
// </summary>
public static string Substr(this string instance, int from, int len)
{
return instance.Substring(from, len);
int max = instance.Length - from;
return instance.Substring(from, len > max ? max : len);
}
// <summary>

View file

@ -68,12 +68,12 @@ MonoString *godot_icall_String_sha256_text(MonoString *p_str) {
}
void godot_register_string_icalls() {
mono_add_internal_call("Godot.String::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer);
mono_add_internal_call("Godot.String::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text);
mono_add_internal_call("Godot.String::godot_icall_String_rfind", (void *)godot_icall_String_rfind);
mono_add_internal_call("Godot.String::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn);
mono_add_internal_call("Godot.String::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer);
mono_add_internal_call("Godot.String::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", (void *)godot_icall_String_rfind);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text);
}
#endif // MONO_GLUE_ENABLED

View file

@ -39,7 +39,8 @@
#define API_SOLUTION_NAME "GodotSharp"
#define CORE_API_ASSEMBLY_NAME "GodotSharp"
#define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor"
#define EDITOR_TOOLS_ASSEMBLY_NAME "GodotSharpTools"
#define TOOLS_ASSEMBLY_NAME "GodotTools"
#define TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME "GodotTools.ProjectEditor"
#define BINDINGS_CLASS_NATIVECALLS "NativeCalls"
#define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls"

View file

@ -59,6 +59,20 @@ String _get_expected_build_config() {
#endif
}
String _get_expected_api_build_config() {
#ifdef TOOLS_ENABLED
return "Debug";
#else
#ifdef DEBUG_ENABLED
return "Debug";
#else
return "Release";
#endif
#endif
}
String _get_mono_user_dir() {
#ifdef TOOLS_ENABLED
if (EditorSettings::get_singleton()) {
@ -88,6 +102,7 @@ class _GodotSharpDirs {
public:
String res_data_dir;
String res_metadata_dir;
String res_assemblies_base_dir;
String res_assemblies_dir;
String res_config_dir;
String res_temp_dir;
@ -118,7 +133,8 @@ private:
_GodotSharpDirs() {
res_data_dir = "res://.mono";
res_metadata_dir = res_data_dir.plus_file("metadata");
res_assemblies_dir = res_data_dir.plus_file("assemblies");
res_assemblies_base_dir = res_data_dir.plus_file("assemblies");
res_assemblies_dir = res_assemblies_base_dir.plus_file(_get_expected_api_build_config());
res_config_dir = res_data_dir.plus_file("etc").plus_file("mono");
// TODO use paths from csproj
@ -231,6 +247,10 @@ String get_res_metadata_dir() {
return _GodotSharpDirs::get_singleton().res_metadata_dir;
}
String get_res_assemblies_base_dir() {
return _GodotSharpDirs::get_singleton().res_assemblies_base_dir;
}
String get_res_assemblies_dir() {
return _GodotSharpDirs::get_singleton().res_assemblies_dir;
}

View file

@ -37,6 +37,7 @@ namespace GodotSharpDirs {
String get_res_data_dir();
String get_res_metadata_dir();
String get_res_assemblies_base_dir();
String get_res_assemblies_dir();
String get_res_config_dir();
String get_res_temp_dir();

View file

@ -52,7 +52,6 @@
#include "gd_mono_utils.h"
#ifdef TOOLS_ENABLED
#include "../editor/godotsharp_editor.h"
#include "main/main.h"
#endif
@ -99,7 +98,7 @@ void gdmono_profiler_init() {
#ifdef DEBUG_ENABLED
static bool _wait_for_debugger_msecs(uint32_t p_msecs) {
bool _wait_for_debugger_msecs(uint32_t p_msecs) {
do {
if (mono_is_debugger_attached())
@ -129,16 +128,17 @@ void gdmono_debug_init() {
bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000);
CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() ||
ProjectSettings::get_singleton()->get_resource_path().empty() ||
Main::is_project_manager()) {
return;
if (da_args.size() == 0)
return;
}
#endif
CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
if (da_args.length() == 0) {
da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) +
",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n"))
@ -207,6 +207,10 @@ void GDMono::initialize() {
print_verbose("Mono: Initializing module...");
char *runtime_build_info = mono_get_runtime_build_info();
print_verbose("Mono JIT compiler version " + String(runtime_build_info));
mono_free(runtime_build_info);
#ifdef DEBUG_METHODS_ENABLED
_initialize_and_check_api_hashes();
#endif
@ -339,18 +343,6 @@ void GDMono::initialize() {
ERR_EXPLAIN("Mono: Failed to load mscorlib assembly");
ERR_FAIL_COND(!_load_corlib_assembly());
#ifdef TOOLS_ENABLED
// The tools domain must be loaded here, before the scripts domain.
// Otherwise domain unload on the scripts domain will hang indefinitely.
ERR_EXPLAIN("Mono: Failed to load tools domain");
ERR_FAIL_COND(_load_tools_domain() != OK);
// TODO move to editor init callback, and do it lazily when required before editor init (e.g.: bindings generation)
ERR_EXPLAIN("Mono: Failed to load Editor Tools assembly");
ERR_FAIL_COND(!_load_editor_tools_assembly());
#endif
ERR_EXPLAIN("Mono: Failed to load scripts domain");
ERR_FAIL_COND(_load_scripts_domain() != OK);
@ -365,8 +357,15 @@ void GDMono::initialize() {
// The following assemblies are not required at initialization
#ifdef MONO_GLUE_ENABLED
if (_load_api_assemblies()) {
// Everything is fine with the api assemblies, load the project assembly
// Everything is fine with the api assemblies, load the tools and project assemblies
#if defined(TOOLS_ENABLED)
ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies");
ERR_FAIL_COND(!_load_tools_assemblies());
#endif
_load_project_assembly();
} else {
if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated))
#ifdef TOOLS_ENABLED
@ -427,10 +426,6 @@ void GDMono::_register_internal_calls() {
#ifdef MONO_GLUE_ENABLED
GodotSharpBindings::register_generated_icalls();
#endif
#ifdef TOOLS_ENABLED
GodotSharpEditor::register_internal_calls();
#endif
}
void GDMono::_initialize_and_check_api_hashes() {
@ -569,6 +564,50 @@ bool GDMono::_load_corlib_assembly() {
return success;
}
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
if (!DirAccess::exists(p_dst_dir)) {
DirAccess *da = DirAccess::create_for_path(p_dst_dir);
Error err = da->make_dir_recursive(p_dst_dir);
memdelete(da);
if (err != OK) {
ERR_PRINTS("Failed to create destination directory for the API assemblies. Error: " + itos(err));
return false;
}
}
String assembly_file = p_assembly_name + ".dll";
String assembly_src = p_src_dir.plus_file(assembly_file);
String assembly_dst = p_dst_dir.plus_file(assembly_file);
if (!FileAccess::exists(assembly_dst) ||
FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) ||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String xml_file = p_assembly_name + ".xml";
if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK)
WARN_PRINTS("Failed to copy " + xml_file);
String pdb_file = p_assembly_name + ".pdb";
if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK)
WARN_PRINTS("Failed to copy " + pdb_file);
Error err = da->copy(assembly_src, assembly_dst);
if (err != OK) {
ERR_PRINTS("Failed to copy " + assembly_file);
return false;
}
GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false);
}
return true;
}
bool GDMono::_load_core_api_assembly() {
if (core_api_assembly)
@ -576,19 +615,31 @@ bool GDMono::_load_core_api_assembly() {
#ifdef TOOLS_ENABLED
if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) {
print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated");
return false;
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
String prebuilt_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_CORE);
if (!FileAccess::exists(prebuilt_dll_path) ||
FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) {
print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated");
return false;
} else {
// Copy the prebuilt Api
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, CORE_API_ASSEMBLY_NAME, APIAssembly::API_CORE) ||
!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) {
print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Core API assembly because it was invalidated");
return false;
}
}
}
#endif
String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll");
if (!FileAccess::exists(assembly_path))
return false;
bool success = load_assembly_from(CORE_API_ASSEMBLY_NAME,
assembly_path,
&core_api_assembly);
bool success = (FileAccess::exists(assembly_path) &&
load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly)) ||
load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly);
if (success) {
#ifdef MONO_GLUE_ENABLED
@ -616,18 +667,29 @@ bool GDMono::_load_editor_api_assembly() {
return true;
if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) {
print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated");
return false;
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
String prebuilt_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_EDITOR);
if (!FileAccess::exists(prebuilt_dll_path) ||
FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) {
print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated");
return false;
} else {
// Copy the prebuilt editor Api (no need to copy the core api if we got to this point)
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) {
print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Editor API assembly because it was invalidated");
return false;
}
}
}
String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
if (!FileAccess::exists(assembly_path))
return false;
bool success = load_assembly_from(EDITOR_API_ASSEMBLY_NAME,
assembly_path,
&editor_api_assembly);
bool success = (FileAccess::exists(assembly_path) &&
load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly)) ||
load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly);
if (success) {
#ifdef MONO_GLUE_ENABLED
@ -643,14 +705,15 @@ bool GDMono::_load_editor_api_assembly() {
#endif
#ifdef TOOLS_ENABLED
bool GDMono::_load_editor_tools_assembly() {
bool GDMono::_load_tools_assemblies() {
if (editor_tools_assembly)
if (tools_assembly && tools_project_editor_assembly)
return true;
_GDMONO_SCOPE_DOMAIN_(tools_domain)
bool success = load_assembly(TOOLS_ASSEMBLY_NAME, &tools_assembly) &&
load_assembly(TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME, &tools_project_editor_assembly);
return load_assembly(EDITOR_TOOLS_ASSEMBLY_NAME, &editor_tools_assembly);
return success;
}
#endif
@ -781,6 +844,14 @@ bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type)
return metadata->get_value(section, "invalidated", false) && modified_time <= stored_modified_time;
}
String GDMono::get_invalidated_api_assembly_path(APIAssembly::Type p_api_type) {
return GodotSharpDirs::get_res_assemblies_dir()
.plus_file(p_api_type == APIAssembly::API_CORE ?
CORE_API_ASSEMBLY_NAME ".dll" :
EDITOR_API_ASSEMBLY_NAME ".dll");
}
#endif
Error GDMono::_load_scripts_domain() {
@ -826,6 +897,8 @@ Error GDMono::_unload_scripts_domain() {
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;
tools_assembly = NULL;
tools_project_editor_assembly = NULL;
#endif
core_api_assembly_out_of_sync = false;
@ -848,22 +921,6 @@ Error GDMono::_unload_scripts_domain() {
return OK;
}
#ifdef TOOLS_ENABLED
Error GDMono::_load_tools_domain() {
ERR_FAIL_COND_V(tools_domain != NULL, ERR_BUG);
print_verbose("Mono: Loading tools domain...");
tools_domain = GDMonoUtils::create_domain("GodotEngine.ToolsDomain");
ERR_EXPLAIN("Mono: Could not create tools app domain");
ERR_FAIL_NULL_V(tools_domain, ERR_CANT_CREATE);
return OK;
}
#endif
#ifdef GD_MONO_HOT_RELOAD
Error GDMono::reload_scripts_domain() {
@ -925,6 +982,11 @@ Error GDMono::reload_scripts_domain() {
}
}
#ifdef TOOLS_ENABLED
ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies");
ERR_FAIL_COND_V(!_load_tools_assemblies(), ERR_CANT_OPEN);
#endif
if (!_load_project_assembly()) {
return ERR_CANT_OPEN;
}
@ -939,7 +1001,7 @@ Error GDMono::reload_scripts_domain() {
Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
CRASH_COND(p_domain == NULL);
CRASH_COND(p_domain == SCRIPTS_DOMAIN); // Should use _unload_scripts_domain() instead
CRASH_COND(p_domain == GDMono::get_singleton()->get_scripts_domain()); // Should use _unload_scripts_domain() instead
String domain_name = mono_domain_get_friendly_name(p_domain);
@ -956,18 +1018,12 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
_domain_assemblies_cleanup(mono_domain_get_id(p_domain));
#ifdef TOOLS_ENABLED
if (p_domain == tools_domain) {
editor_tools_assembly = NULL;
}
#endif
MonoException *exc = NULL;
mono_domain_try_unload(p_domain, (MonoObject **)&exc);
if (exc) {
ERR_PRINTS("Exception thrown when unloading domain `" + domain_name + "`");
GDMonoUtils::debug_unhandled_exception(exc);
GDMonoUtils::debug_print_unhandled_exception(exc);
return FAILED;
}
@ -998,6 +1054,22 @@ GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) {
return NULL;
}
GDMonoClass *GDMono::get_class(const StringName &p_namespace, const StringName &p_name) {
uint32_t domain_id = mono_domain_get_id(mono_domain_get());
HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id];
const String *k = NULL;
while ((k = domain_assemblies.next(k))) {
GDMonoAssembly *assembly = domain_assemblies.get(*k);
GDMonoClass *klass = assembly->get_class(p_namespace, p_name);
if (klass)
return klass;
}
return NULL;
}
void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) {
HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[p_domain_id];
@ -1038,9 +1110,6 @@ GDMono::GDMono() {
root_domain = NULL;
scripts_domain = NULL;
#ifdef TOOLS_ENABLED
tools_domain = NULL;
#endif
core_api_assembly_out_of_sync = false;
#ifdef TOOLS_ENABLED
@ -1052,7 +1121,8 @@ GDMono::GDMono() {
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;
editor_tools_assembly = NULL;
tools_assembly = NULL;
tools_project_editor_assembly = NULL;
#endif
api_core_hash = 0;
@ -1064,16 +1134,6 @@ GDMono::GDMono() {
GDMono::~GDMono() {
if (is_runtime_initialized()) {
#ifdef TOOLS_ENABLED
if (tools_domain) {
Error err = finalize_and_unload_domain(tools_domain);
if (err != OK) {
ERR_PRINT("Mono: Failed to unload tools domain");
}
}
#endif
if (scripts_domain) {
Error err = _unload_scripts_domain();
if (err != OK) {
@ -1128,14 +1188,14 @@ int32_t _GodotSharp::get_domain_id() {
int32_t _GodotSharp::get_scripts_domain_id() {
MonoDomain *domain = SCRIPTS_DOMAIN;
MonoDomain *domain = GDMono::get_singleton()->get_scripts_domain();
CRASH_COND(!domain); // User must check if scripts domain is loaded before calling this method
return mono_domain_get_id(domain);
}
bool _GodotSharp::is_scripts_domain_loaded() {
return GDMono::get_singleton()->is_runtime_initialized() && SCRIPTS_DOMAIN != NULL;
return GDMono::get_singleton()->is_runtime_initialized() && GDMono::get_singleton()->get_scripts_domain() != NULL;
}
bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) {
@ -1157,7 +1217,7 @@ bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) {
if (!p_domain)
return true;
if (p_domain == SCRIPTS_DOMAIN && GDMono::get_singleton()->is_finalizing_scripts_domain())
if (p_domain == GDMono::get_singleton()->get_scripts_domain() && GDMono::get_singleton()->is_finalizing_scripts_domain())
return true;
return mono_domain_is_unloading(p_domain);
}
@ -1172,6 +1232,12 @@ bool _GodotSharp::is_runtime_initialized() {
return GDMono::get_singleton()->is_runtime_initialized();
}
void _GodotSharp::_reload_assemblies(bool p_soft_reload) {
#ifdef GD_MONO_HOT_RELOAD
CSharpLanguage::get_singleton()->reload_assemblies(p_soft_reload);
#endif
}
void _GodotSharp::_bind_methods() {
ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread);
@ -1184,6 +1250,7 @@ void _GodotSharp::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &_GodotSharp::is_runtime_shutting_down);
ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &_GodotSharp::is_runtime_initialized);
ClassDB::bind_method(D_METHOD("_reload_assemblies"), &_GodotSharp::_reload_assemblies);
}
_GodotSharp::_GodotSharp() {

View file

@ -78,11 +78,6 @@ struct Version {
String to_string(Type p_type);
} // namespace APIAssembly
#define SCRIPTS_DOMAIN GDMono::get_singleton()->get_scripts_domain()
#ifdef TOOLS_ENABLED
#define TOOLS_DOMAIN GDMono::get_singleton()->get_tools_domain()
#endif
class GDMono {
bool runtime_initialized;
@ -90,9 +85,6 @@ class GDMono {
MonoDomain *root_domain;
MonoDomain *scripts_domain;
#ifdef TOOLS_ENABLED
MonoDomain *tools_domain;
#endif
bool core_api_assembly_out_of_sync;
#ifdef TOOLS_ENABLED
@ -104,7 +96,8 @@ class GDMono {
GDMonoAssembly *project_assembly;
#ifdef TOOLS_ENABLED
GDMonoAssembly *editor_api_assembly;
GDMonoAssembly *editor_tools_assembly;
GDMonoAssembly *tools_assembly;
GDMonoAssembly *tools_project_editor_assembly;
#endif
HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies;
@ -115,7 +108,7 @@ class GDMono {
bool _load_core_api_assembly();
#ifdef TOOLS_ENABLED
bool _load_editor_api_assembly();
bool _load_editor_tools_assembly();
bool _load_tools_assemblies();
#endif
bool _load_project_assembly();
@ -132,10 +125,6 @@ class GDMono {
Error _load_scripts_domain();
Error _unload_scripts_domain();
#ifdef TOOLS_ENABLED
Error _load_tools_domain();
#endif
uint64_t api_core_hash;
#ifdef TOOLS_ENABLED
uint64_t api_editor_hash;
@ -170,6 +159,7 @@ public:
#ifdef TOOLS_ENABLED
void metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated);
bool metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type);
String get_invalidated_api_assembly_path(APIAssembly::Type p_api_type);
#endif
static GDMono *get_singleton() { return singleton; }
@ -185,16 +175,14 @@ public:
_FORCE_INLINE_ bool is_finalizing_scripts_domain() { return finalizing_scripts_domain; }
_FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; }
#ifdef TOOLS_ENABLED
_FORCE_INLINE_ MonoDomain *get_tools_domain() { return tools_domain; }
#endif
_FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; }
#ifdef TOOLS_ENABLED
_FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_editor_tools_assembly() const { return editor_tools_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; }
#endif
#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
@ -202,6 +190,7 @@ public:
#endif
GDMonoClass *get_class(MonoClass *p_raw_class);
GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name);
#ifdef GD_MONO_HOT_RELOAD
Error reload_scripts_domain();
@ -276,6 +265,8 @@ class _GodotSharp : public Object {
List<NodePath *> np_delete_queue;
List<RID *> rid_delete_queue;
void _reload_assemblies(bool p_soft_reload);
protected:
static _GodotSharp *singleton;
static void _bind_methods();

View file

@ -46,6 +46,20 @@ 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;
@ -67,11 +81,19 @@ 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));
r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_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());
#ifdef TOOLS_ENABLED
r_search_dirs.push_back(GodotSharpDirs::get_data_editor_tools_dir());
// For GodotTools to find the api assemblies
r_search_dirs.push_back(GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"));
#endif
}

View file

@ -41,7 +41,7 @@ String GDMonoClass::get_full_name(MonoClass *p_mono_class) {
MonoException *exc = NULL;
MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return GDMonoMarshal::mono_string_to_godot(str);
}
@ -74,16 +74,13 @@ bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const {
}
GDMonoClass *GDMonoClass::get_parent_class() {
MonoClass *parent_mono_class = mono_class_get_parent(mono_class);
return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : NULL;
}
if (assembly) {
MonoClass *parent_mono_class = mono_class_get_parent(mono_class);
if (parent_mono_class) {
return GDMono::get_singleton()->get_class(parent_mono_class);
}
}
return NULL;
GDMonoClass *GDMonoClass::get_nesting_class() {
MonoClass *nesting_type = mono_class_get_nesting_type(mono_class);
return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : NULL;
}
#ifdef TOOLS_ENABLED

View file

@ -121,6 +121,7 @@ public:
_FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; }
GDMonoClass *get_parent_class();
GDMonoClass *get_nesting_class();
#ifdef TOOLS_ENABLED
Vector<MonoClassField *> get_enum_fields();

View file

@ -315,7 +315,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
// The order in which we check the following interfaces is very important (dictionaries and generics first)
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
MonoReflectionType *key_reftype, *value_reftype;
if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
@ -340,9 +340,15 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
}
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
break;
if (GDMonoUtils::tools_godot_api_check()) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
break;
} else {
MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array());
mono_field_set_value(p_object, mono_field, managed);
break;
}
}
ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + type_class->get_name());
@ -450,7 +456,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
} break;
case MONO_TYPE_GENERICINST: {
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), type.type_class);
@ -489,9 +495,15 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
}
if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
break;
if (GDMonoUtils::tools_godot_api_check()) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
break;
} else {
MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array());
mono_field_set_value(p_object, mono_field, managed);
break;
}
}
} break;

View file

@ -74,15 +74,14 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass);
script_binding.wrapper_class = klass;
script_binding.gchandle = MonoGCHandle::create_strong(managed);
script_binding.owner = unmanaged;
Reference *kref = Object::cast_to<Reference>(unmanaged);
if (kref) {
if (ref) {
// Unsafe refcount increment. The managed instance also counts as a reference.
// This way if the unmanaged world has no references to our owner
// but the managed instance is alive, the refcount will be 1 instead of 0.
// See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
kref->reference();
ref->reference();
}
// The object was just created, no script instance binding should have been attached

View file

@ -159,7 +159,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
// The order in which we check the following interfaces is very important (dictionaries and generics first)
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
return Variant::DICTIONARY;
@ -179,7 +179,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
} break;
case MONO_TYPE_GENERICINST: {
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
return Variant::DICTIONARY;
@ -217,7 +217,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type) {
switch (p_array_type.type_encoding) {
case MONO_TYPE_GENERICINST: {
MonoReflectionType *array_reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_array_type.type_class->get_mono_type());
MonoReflectionType *array_reftype = mono_type_get_object(mono_domain_get(), p_array_type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_array(array_reftype)) {
MonoReflectionType *elem_reftype;
@ -244,7 +244,7 @@ bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_
bool try_get_dictionary_key_value_types(const ManagedType &p_dictionary_type, ManagedType &r_key_type, ManagedType &r_value_type) {
switch (p_dictionary_type.type_encoding) {
case MONO_TYPE_GENERICINST: {
MonoReflectionType *dict_reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_dictionary_type.type_class->get_mono_type());
MonoReflectionType *dict_reftype = mono_type_get_object(mono_domain_get(), p_dictionary_type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_dictionary(dict_reftype)) {
MonoReflectionType *key_reftype;
@ -539,7 +539,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
// The order in which we check the following interfaces is very important (dictionaries and generics first)
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
MonoReflectionType *key_reftype, *value_reftype;
if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
@ -558,7 +558,11 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
if (GDMonoUtils::tools_godot_api_check()) {
return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
} else {
return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array());
}
}
} break;
case MONO_TYPE_OBJECT: {
@ -652,7 +656,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
break;
case MONO_TYPE_GENERICINST: {
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), p_type.type_class);
@ -681,7 +685,11 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
if (GDMonoUtils::tools_godot_api_check()) {
return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
} else {
return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array());
}
}
} break;
} break;
@ -831,20 +839,20 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
if (CACHED_CLASS(Array) == type_class) {
MonoException *exc = NULL;
Array *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Array, GetPtr), p_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant();
}
if (CACHED_CLASS(Dictionary) == type_class) {
MonoException *exc = NULL;
Dictionary *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Dictionary, GetPtr), p_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant();
}
// The order in which we check the following interfaces is very important (dictionaries and generics first)
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
return GDMonoUtils::Marshal::generic_idictionary_to_dictionary(p_obj);
@ -864,19 +872,19 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
} break;
case MONO_TYPE_GENERICINST: {
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
MonoException *exc = NULL;
MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return *unbox<Dictionary *>(ret);
}
if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
MonoException *exc = NULL;
MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return *unbox<Array *>(ret);
}

View file

@ -125,6 +125,7 @@ void MonoCache::clear_godot_api_cache() {
class_Array = NULL;
class_Dictionary = NULL;
class_MarshalUtils = NULL;
class_ISerializationListener = NULL;
#ifdef DEBUG_ENABLED
class_DebuggingUtils = NULL;
@ -242,6 +243,7 @@ void update_godot_api_cache() {
CACHE_CLASS_AND_CHECK(Array, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array));
CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary));
CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils));
CACHE_CLASS_AND_CHECK(ISerializationListener, GODOT_API_CLASS(ISerializationListener));
#ifdef DEBUG_ENABLED
CACHE_CLASS_AND_CHECK(DebuggingUtils, GODOT_API_CLASS(DebuggingUtils));
@ -302,7 +304,7 @@ void update_godot_api_cache() {
#endif
// TODO Move to CSharpLanguage::init() and do handle disposal
MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler));
mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler);
@ -371,7 +373,6 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
// This way if the unmanaged world has no references to our owner
// but the managed instance is alive, the refcount will be 1 instead of 0.
// See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
ref->reference();
}
@ -384,7 +385,7 @@ void set_main_thread(MonoThread *p_thread) {
void attach_current_thread() {
ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized());
MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN);
MonoThread *mono_thread = mono_thread_attach(mono_domain_get());
ERR_FAIL_NULL(mono_thread);
}
@ -448,17 +449,12 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) {
}
MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object) {
String object_type = p_object->get_class_name();
if (object_type[0] == '_')
object_type = object_type.substr(1, object_type.length());
if (!ClassDB::is_parent_class(object_type, p_native)) {
if (!ClassDB::is_parent_class(p_object->get_class_name(), p_native)) {
ERR_EXPLAIN("Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'");
ERR_FAIL_V(NULL);
}
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
ERR_FAIL_NULL_V(mono_object, NULL);
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object);
@ -470,7 +466,7 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa
}
MonoObject *create_managed_from(const NodePath &p_from) {
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(NodePath));
MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(NodePath));
ERR_FAIL_NULL_V(mono_object, NULL);
// Construct
@ -482,7 +478,7 @@ MonoObject *create_managed_from(const NodePath &p_from) {
}
MonoObject *create_managed_from(const RID &p_from) {
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(RID));
MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(RID));
ERR_FAIL_NULL_V(mono_object, NULL);
// Construct
@ -494,7 +490,7 @@ MonoObject *create_managed_from(const RID &p_from) {
}
MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
ERR_FAIL_NULL_V(mono_object, NULL);
// Search constructor that takes a pointer as parameter
@ -518,13 +514,13 @@ MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
MonoException *exc = NULL;
GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return mono_object;
}
MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) {
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
ERR_FAIL_NULL_V(mono_object, NULL);
// Search constructor that takes a pointer as parameter
@ -548,7 +544,7 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class)
MonoException *exc = NULL;
GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return mono_object;
}
@ -667,7 +663,10 @@ void print_unhandled_exception(MonoException *p_exc) {
}
void set_pending_exception(MonoException *p_exc) {
#ifdef HAS_PENDING_EXCEPTIONS
#ifdef NO_PENDING_EXCEPTIONS
debug_unhandled_exception(p_exc);
GD_UNREACHABLE();
#else
if (get_runtime_invoke_count() == 0) {
debug_unhandled_exception(p_exc);
GD_UNREACHABLE();
@ -677,9 +676,6 @@ void set_pending_exception(MonoException *p_exc) {
ERR_PRINTS("Exception thrown from managed code, but it could not be set as pending:");
GDMonoUtils::debug_print_unhandled_exception(p_exc);
}
#else
debug_unhandled_exception(p_exc);
GD_UNREACHABLE();
#endif
}
@ -755,113 +751,137 @@ void dispose(MonoObject *p_mono_object, MonoException **r_exc) {
namespace Marshal {
MonoBoolean type_is_generic_array(MonoReflectionType *p_reftype) {
#ifdef MONO_GLUE_ENABLED
#ifdef TOOLS_ENABLED
#define NO_GLUE_RET(m_ret) \
{ \
if (!mono_cache.godot_api_cache_updated) return m_ret; \
}
#else
#define NO_GLUE_RET(m_ret) \
{}
#endif
#else
#define NO_GLUE_RET(m_ret) \
{ return m_ret; }
#endif
bool type_is_generic_array(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
TypeIsGenericArray thunk = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
MonoBoolean type_is_generic_dictionary(MonoReflectionType *p_reftype) {
bool type_is_generic_dictionary(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
TypeIsGenericDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype) {
ArrayGetElementType thunk = CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType);
MonoException *exc = NULL;
invoke_method_thunk(thunk, p_array_reftype, r_elem_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
}
void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
DictionaryGetKeyValueTypes thunk = CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes);
MonoException *exc = NULL;
invoke_method_thunk(thunk, p_dict_reftype, r_key_reftype, r_value_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
}
MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) {
bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
GenericIEnumerableIsAssignableFromType thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) {
bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
GenericIDictionaryIsAssignableFromType thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype) {
bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype) {
NO_GLUE_RET(false);
GenericIEnumerableIsAssignableFromType_with_info thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, r_elem_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
NO_GLUE_RET(false);
GenericIDictionaryIsAssignableFromType_with_info thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, r_key_reftype, r_value_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
Array enumerable_to_array(MonoObject *p_enumerable) {
NO_GLUE_RET(Array());
Array result;
EnumerableToArray thunk = CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray);
MonoException *exc = NULL;
invoke_method_thunk(thunk, p_enumerable, &result, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return result;
}
Dictionary idictionary_to_dictionary(MonoObject *p_idictionary) {
NO_GLUE_RET(Dictionary());
Dictionary result;
IDictionaryToDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary);
MonoException *exc = NULL;
invoke_method_thunk(thunk, p_idictionary, &result, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return result;
}
Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary) {
NO_GLUE_RET(Dictionary());
Dictionary result;
GenericIDictionaryToDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryToDictionary);
MonoException *exc = NULL;
invoke_method_thunk(thunk, p_generic_idictionary, &result, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return result;
}
GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype) {
NO_GLUE_RET(NULL);
MakeGenericArrayType thunk = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericArrayType);
MonoException *exc = NULL;
MonoReflectionType *reftype = invoke_method_thunk(thunk, p_elem_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype)));
}
GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) {
NO_GLUE_RET(NULL);
MakeGenericDictionaryType thunk = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericDictionaryType);
MonoException *exc = NULL;
MonoReflectionType *reftype = invoke_method_thunk(thunk, p_key_reftype, p_value_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype)));
}
} // namespace Marshal
// namespace Marshal
} // namespace GDMonoUtils

View file

@ -41,7 +41,7 @@
#include "core/object.h"
#include "core/reference.h"
#define UNLIKELY_UNHANDLED_EXCEPTION(m_exc) \
#define UNHANDLED_EXCEPTION(m_exc) \
if (unlikely(m_exc != NULL)) { \
GDMonoUtils::debug_unhandled_exception(m_exc); \
GD_UNREACHABLE(); \
@ -78,16 +78,16 @@ typedef void (*GenericIDictionaryToDictionary)(MonoObject *, Dictionary *, MonoE
namespace Marshal {
MonoBoolean type_is_generic_array(MonoReflectionType *p_reftype);
MonoBoolean type_is_generic_dictionary(MonoReflectionType *p_reftype);
bool type_is_generic_array(MonoReflectionType *p_reftype);
bool type_is_generic_dictionary(MonoReflectionType *p_reftype);
void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype);
void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype);
MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype);
MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype);
MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype);
MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype);
bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype);
bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype);
bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype);
bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype);
GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype);
GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype);
@ -157,6 +157,7 @@ struct MonoCache {
GDMonoClass *class_Array;
GDMonoClass *class_Dictionary;
GDMonoClass *class_MarshalUtils;
GDMonoClass *class_ISerializationListener;
#ifdef DEBUG_ENABLED
GDMonoClass *class_DebuggingUtils;
@ -235,10 +236,19 @@ void update_godot_api_cache();
inline void clear_corlib_cache() {
mono_cache.clear_corlib_cache();
}
inline void clear_godot_api_cache() {
mono_cache.clear_godot_api_cache();
}
_FORCE_INLINE_ bool tools_godot_api_check() {
#ifdef TOOLS_ENABLED
return mono_cache.godot_api_cache_updated;
#else
return true; // Assume it's updated if this was called, otherwise it's a bug
#endif
}
_FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) {
p_hash ^= p_with_hash + 0x9e3779b9 + (p_hash << 6) + (p_hash >> 2);
}

View file

@ -91,7 +91,7 @@ Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argc
set_completed(true);
int signal_argc = p_argcount - 1;
MonoArray *signal_args = mono_array_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(MonoObject), signal_argc);
MonoArray *signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), signal_argc);
for (int i = 0; i < signal_argc; i++) {
MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_args[i]);