Commit 270af6fa by Ignacio Etcheverry

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.
parent 7b569e91
......@@ -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);
......
......@@ -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() {
......
#!/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
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)
import build_scripts.godotsharptools_build as godotsharptools_build
# Build GodotTools
godotsharptools_build.build(env_mono)
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)
# 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)
# 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)
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
# 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):
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])
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
from shutil import copyfile
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)
sln_path = os.path.abspath(str(source[0]))
target_path = os.path.abspath(str(target[0]))
# 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 = [
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')
msbuild_args = [solution_path, '/p:Configuration=' + build_config]
msbuild_args += ['/p:FrameworkPathOverride=' + framework_path] if framework_path else []
msbuild_args += extra_msbuild_args
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')
......@@ -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);
......
# nuget packages
packages
\ No newline at end of file
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();
}
}
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
# 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/
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;
}
}
<?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>
\ No newline at end of file
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")]
<?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>
<ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>GodotSharpTools</RootNamespace>
<AssemblyName>GodotSharpTools</AssemblyName>
<RootNamespace>GodotTools.Core</RootNamespace>
<AssemblyName>GodotTools.Core</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
......@@ -21,7 +20,6 @@
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
......@@ -30,26 +28,11 @@
</PropertyGroup>
<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>
</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="ProcessExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils\OS.cs" />
<Compile Include="Editor\GodotSharpExport.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<Compile Include="StringExtensions.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
\ No newline at end of file
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;
}
}
}
}
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("")]
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;
}
}
}
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
}
}
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();
}
}
}
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";
}
}
<?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>GodotTools.ProjectEditor</RootNamespace>
<AssemblyName>GodotTools.ProjectEditor</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
</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="Microsoft.Build" />
<Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL">
<!--
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="ApiAssembliesInfo.cs" />
<Compile Include="ApiSolutionGenerator.cs" />
<Compile Include="DotNetSolution.cs" />
<Compile Include="Properties\AssemblyInfo.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>
......@@ -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",
......
using GodotTools.Core;
using System;
using DotNet.Globbing;
using Microsoft.Build.Construction;
namespace GodotSharpTools.Project
namespace GodotTools.ProjectEditor
{
public static class ProjectExtensions
{
......
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.
......
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)
{
......
......@@ -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("")]
......
<?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>
\ No newline at end of file
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";
}
}
}
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;
}
}
}
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);
}
}
}
}
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);
}
}
<?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>
\ No newline at end of file
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();
}
}
}
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();
}
}
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);
}
}
}
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
}
}
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();
}
}
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;
}
}
}
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;
}
}
}
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;
}
}
}
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"}
};
}
}
......
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("")]
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;
}
}
}
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);
}
}
}
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());
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
namespace GodotSharpTools.Utils
namespace GodotTools.Utils
{
public static class OS
{
......@@ -46,17 +49,79 @@ namespace GodotSharpTools.Utils
return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
static bool? IsUnixCache = null;
static readonly string[] UnixPlatforms = new string[] { HaikuName, OSXName, ServerName, X11Name };
private static bool? _isUnixCache;
private static readonly string[] UnixPlatforms = {HaikuName, OSXName, ServerName, X11Name};
public static bool IsUnix()
{
if (IsUnixCache.HasValue)
return IsUnixCache.Value;
if (_isUnixCache.HasValue)
return _isUnixCache.Value;
string osName = GetPlatformName();
IsUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
return IsUnixCache.Value;
_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();
}
}
}
}
......@@ -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();
......
......@@ -44,66 +44,54 @@
namespace CSharpProject {
String generate_core_api_project(const String &p_dir, 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 compile_items = p_files;
const Variant *args[2] = { &dir, &compile_items };
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) {
GDMonoClass *klass = p_tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ApiSolutionGenerator");
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) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator");
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);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL_V(String());
}
return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : 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) {
String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files) {
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);
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
_GDMONO_SCOPE_DOMAIN_(temp_domain);
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator");
GDMonoAssembly *tools_project_editor_assembly = NULL;
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 (!GDMono::get_singleton()->load_assembly("GodotTools.ProjectEditor", &tools_project_editor_assembly)) {
ERR_EXPLAIN("Failed to load assembly: 'GodotTools.ProjectEditor'");
ERR_FAIL_V(false);
}
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL_V(String());
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();
}
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
......@@ -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
/*************************************************************************/
/* 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;
}
/*************************************************************************/
/* 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
/*************************************************************************/
/* 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
/*************************************************************************/
/* 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
/*************************************************************************/
/* 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment