Browse Source

Buildsystem improvements for the Mono module
- Make sure to search the mono installation directory for the right architecture in the windows registry.
- Do not build GodotSharpTools directly to #bin dir. Instead build to the default output path and copy it. This way we avoid MSBuild adding files we don't want to #bin.
- Add hint path for MSBuild in OSX.
- Copy shared library on Unix if not statically linking.
- Use vswhere to search MSBuild and search for 14.0 tools version in the registry instead of 4.0.
- SCons will only fallback xbuild when msbuild is not found if 'xbuild_fallback=yes' is passed to the command.
- Use mono's assembly path as FrameworkPathOverride if using with system's MSBuild (not mono's fork).
- Cleanup.

Ignacio Etcheverry 7 years ago
parent
commit
9f469887fc

+ 110 - 29
modules/mono/SCsub

@@ -53,68 +53,149 @@ if env['tools']:
 
 vars = Variables()
 vars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True))
+vars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False))
 vars.Update(env)
 
 # Glue sources
 if env['mono_glue']:
     env.add_source_files(env.modules_sources, 'glue/*.cpp')
 else:
-    env.Append(CPPDEFINES = [ 'MONO_GLUE_DISABLED' ])
+    env.Append(CPPDEFINES=['MONO_GLUE_DISABLED'])
 
 if ARGUMENTS.get('yolo_copy', False):
-    env.Append(CPPDEFINES = [ 'YOLO_COPY' ])
+    env.Append(CPPDEFINES=['YOLO_COPY'])
+
 
 # Build GodotSharpTools solution
 
+
 import os
-import subprocess
-import mono_reg_utils as monoreg
+
+
+def find_msbuild_unix(filename):
+    import os.path
+    import sys
+
+    hint_dirs = ['/opt/novell/mono/bin']
+    if sys.platform == "darwin":
+        hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin'] + hint_dirs
+
+    for hint_dir in hint_dirs:
+        hint_path = os.path.join(hint_dir, filename)
+        if os.path.isfile(hint_path):
+            return hint_path
+
+    for hint_dir in os.environ["PATH"].split(os.pathsep):
+        hint_dir = hint_dir.strip('"')
+        hint_path = os.path.join(hint_dir, filename)
+        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+            return hint_path
+
+    return None
+
+
+def find_msbuild_windows():
+    import mono_reg_utils as monoreg
+
+    msbuild_tools_path = monoreg.find_msbuild_tools_path_reg()
+
+    if msbuild_tools_path:
+        return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), '')
+    else:
+        bits = env['bits']
+
+        if bits == '32':
+            if os.getenv('MONO32_PREFIX'):
+                mono_root = os.getenv('MONO32_PREFIX')
+            else:
+                mono_root = monoreg.find_mono_root_dir(bits)
+        else:
+            if os.getenv('MONO64_PREFIX'):
+                mono_root = os.getenv('MONO64_PREFIX')
+            else:
+                mono_root = monoreg.find_mono_root_dir(bits)
+
+        if mono_root:
+            msbuild_mono = os.path.join(mono_root, 'bin', 'msbuild.bat')
+
+            if os.path.isfile(msbuild_mono):
+                return (msbuild_mono, os.path.join(mono_root, 'lib', 'mono', '4.5'))
+
+    return None
 
 
 def mono_build_solution(source, target, env):
+    import subprocess
+    import mono_reg_utils as monoreg
+    from shutil import copyfile
+
+    framework_path_override = ''
+
     if os.name == 'nt':
-        msbuild_tools_path = monoreg.find_msbuild_tools_path_reg()
-        if not msbuild_tools_path:
-            raise RuntimeError('Cannot find MSBuild Tools Path in the registry')
-        msbuild_path = os.path.join(msbuild_tools_path, 'MSBuild.exe')
+        msbuild_info = find_msbuild_windows()
+        if msbuild_info is None:
+            raise RuntimeError('Cannot find MSBuild executable')
+        msbuild_path = msbuild_windows[0]
+        framework_path_override = msbuild_windows[1]
     else:
-        msbuild_path = 'msbuild'
+        msbuild_path = find_msbuild_unix('msbuild')
+        if msbuild_path is None:
+            xbuild_fallback = env['xbuild_fallback']
+
+            if xbuild_fallback and os.name == 'nt':
+                print("Option 'xbuild_fallback' not supported on Windows")
+                xbuild_fallback = False
+
+            if xbuild_fallback:
+                print('Cannot find MSBuild executable, trying with xbuild')
+                print('Warning: xbuild is deprecated')
+
+                msbuild_path = find_msbuild_unix('xbuild')
+
+                if msbuild_path is None:
+                    raise RuntimeError('Cannot find xbuild executable')
+            else:
+                raise RuntimeError('Cannot find MSBuild executable')
+
+    print('MSBuild path: ' + msbuild_path)
 
-    output_path = os.path.abspath(os.path.join(str(target[0]), os.pardir))
+    build_config = 'Release'
 
     msbuild_args = [
         msbuild_path,
         os.path.abspath(str(source[0])),
-        '/p:Configuration=Release',
-        '/p:OutputPath=' + output_path
+        '/p:Configuration=' + build_config,
     ]
 
+    if framework_path_override:
+        msbuild_args += ['/p:FrameworkPathOverride=' + framework_path_override]
+
     msbuild_env = os.environ.copy()
 
     # Needed when running from Developer Command Prompt for VS
     if 'PLATFORM' in msbuild_env:
         del msbuild_env['PLATFORM']
 
-    msbuild_alt_paths = [ 'xbuild' ]
-
-    while True:
-        try:
-            subprocess.check_call(msbuild_args, env = msbuild_env)
-            break
-        except subprocess.CalledProcessError:
-            raise RuntimeError('GodotSharpTools build failed')
-        except OSError:
-            if os.name != 'nt':
-                if not msbuild_alt_paths:
-                    raise RuntimeError('Could not find commands msbuild or xbuild')
-                # Try xbuild
-                msbuild_args[0] = msbuild_alt_paths.pop(0)
-            else:
-                raise RuntimeError('Could not find command MSBuild.exe')
+    try:
+        subprocess.check_call(msbuild_args, env=msbuild_env)
+    except subprocess.CalledProcessError:
+        raise RuntimeError('GodotSharpTools build failed')
+
+    src_dir = os.path.abspath(os.path.join(str(source[0]), os.pardir, 'bin', build_config))
+    dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
+
+    if not os.path.isdir(dst_dir):
+        if os.path.exists(dst_dir):
+            raise RuntimeError('Target directory is a file')
+        os.makedirs(dst_dir)
+
+    asm_file = 'GodotSharpTools.dll'
+
+    copyfile(os.path.join(src_dir, asm_file), os.path.join(dst_dir, asm_file))
 
 
 mono_sln_builder = Builder(action = mono_build_solution)
-env.Append(BUILDERS = { 'MonoBuildSolution' : mono_sln_builder })
+env.Append(BUILDERS={'MonoBuildSolution': mono_sln_builder})
 env.MonoBuildSolution(
     os.path.join(Dir('#bin').abspath, 'GodotSharpTools.dll'),
     'editor/GodotSharpTools/GodotSharpTools.sln'

+ 58 - 22
modules/mono/config.py

@@ -2,7 +2,6 @@
 import imp
 import os
 import sys
-from shutil import copyfile
 
 from SCons.Script import BoolVariable, Environment, Variables
 
@@ -16,8 +15,7 @@ def find_file_in_dir(directory, files, prefix='', extension=''):
     for curfile in files:
         if os.path.isfile(os.path.join(directory, prefix + curfile + extension)):
             return curfile
-
-    return None
+    return ''
 
 
 def can_build(platform):
@@ -31,6 +29,22 @@ def is_enabled():
     return False
 
 
+def copy_file_no_replace(src_dir, dst_dir, name):
+    from shutil import copyfile
+
+    src_path = os.path.join(src_dir, name)
+    dst_path = os.path.join(dst_dir, name)
+    need_copy = True
+
+    if not os.path.isdir(dst_dir):
+        os.mkdir(dst_dir)
+    elif os.path.exists(dst_path):
+        need_copy = False
+
+    if need_copy:
+        copyfile(src_path, dst_path)
+
+
 def configure(env):
     env.use_ptrcall = True
 
@@ -38,6 +52,8 @@ def configure(env):
     envvars.Add(BoolVariable('mono_static', 'Statically link mono', False))
     envvars.Update(env)
 
+    bits = env['bits']
+
     mono_static = env['mono_static']
 
     mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0']
@@ -46,18 +62,18 @@ def configure(env):
         if mono_static:
             raise RuntimeError('mono-static: Not supported on Windows')
 
-        if env['bits'] == '32':
+        if bits == '32':
             if os.getenv('MONO32_PREFIX'):
                 mono_root = os.getenv('MONO32_PREFIX')
             elif os.name == 'nt':
-                mono_root = monoreg.find_mono_root_dir()
+                mono_root = monoreg.find_mono_root_dir(bits)
         else:
             if os.getenv('MONO64_PREFIX'):
                 mono_root = os.getenv('MONO64_PREFIX')
             elif os.name == 'nt':
-                mono_root = monoreg.find_mono_root_dir()
+                mono_root = monoreg.find_mono_root_dir(bits)
 
-        if mono_root is None:
+        if not mono_root:
             raise RuntimeError('Mono installation directory not found')
 
         mono_lib_path = os.path.join(mono_root, 'lib')
@@ -67,7 +83,7 @@ def configure(env):
 
         mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib')
 
-        if mono_lib_name is None:
+        if not mono_lib_name:
             raise RuntimeError('Could not find mono library in: ' + mono_lib_path)
 
         if os.getenv('VCINSTALLDIR'):
@@ -79,28 +95,23 @@ def configure(env):
 
         mono_dll_name = find_file_in_dir(mono_bin_path, mono_lib_names, extension='.dll')
 
-        mono_dll_src = os.path.join(mono_bin_path, mono_dll_name + '.dll')
-        mono_dll_dst = os.path.join('bin', mono_dll_name + '.dll')
-        copy_mono_dll = True
-
-        if not os.path.isdir('bin'):
-            os.mkdir('bin')
-        elif os.path.exists(mono_dll_dst):
-            copy_mono_dll = False
+        if not mono_dll_name:
+            raise RuntimeError('Could not find mono shared library in: ' + mono_bin_path)
 
-        if copy_mono_dll:
-            copyfile(mono_dll_src, mono_dll_dst)
+        copy_file_no_replace(mono_bin_path, 'bin', mono_dll_name + '.dll')
     else:
-        mono_root = None
+        sharedlib_ext = '.dylib' if sys.platform == 'darwin' else '.so'
 
-        if env['bits'] == '32':
+        mono_root = ''
+
+        if bits == '32':
             if os.getenv('MONO32_PREFIX'):
                 mono_root = os.getenv('MONO32_PREFIX')
         else:
             if os.getenv('MONO64_PREFIX'):
                 mono_root = os.getenv('MONO64_PREFIX')
 
-        if mono_root is not None:
+        if mono_root:
             mono_lib_path = os.path.join(mono_root, 'lib')
 
             env.Append(LIBPATH=mono_lib_path)
@@ -108,7 +119,7 @@ def configure(env):
 
             mono_lib = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension='.a')
 
-            if mono_lib is None:
+            if not mono_lib:
                 raise RuntimeError('Could not find mono library in: ' + mono_lib_path)
 
             env.Append(CPPFLAGS=['-D_REENTRANT'])
@@ -130,12 +141,37 @@ def configure(env):
             elif sys.platform == "linux" or sys.platform == "linux2":
                 env.Append(LIBS=['m', 'rt', 'dl', 'pthread'])
 
+            if not mono_static:
+                mono_so_name = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension=sharedlib_ext)
+
+                if not mono_so_name:
+                    raise RuntimeError('Could not find mono shared library in: ' + mono_lib_path)
+
+                copy_file_no_replace(mono_lib_path, 'bin', 'lib' + mono_so_name + sharedlib_ext)
         else:
             if mono_static:
                 raise RuntimeError('mono-static: Not supported with pkg-config. Specify a mono prefix manually')
 
             env.ParseConfig('pkg-config monosgen-2 --cflags --libs')
 
+            mono_lib_path = ''
+            mono_so_name = ''
+
+            tmpenv = Environment()
+            tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L')
+
+            for hint_dir in tmpenv['LIBPATH']:
+                name_found = find_file_in_dir(hint_dir, mono_lib_names, prefix='lib', extension=sharedlib_ext)
+                if name_found:
+                    mono_lib_path = hint_dir
+                    mono_so_name = name_found
+                    break
+
+            if not mono_so_name:
+                raise RuntimeError('Could not find mono shared library in: ' + str(tmpenv['LIBPATH']))
+
+            copy_file_no_replace(mono_lib_path, 'bin', 'lib' + mono_so_name + sharedlib_ext)
+
         env.Append(LINKFLAGS='-rdynamic')
 
 

+ 54 - 19
modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs

@@ -4,6 +4,7 @@ using System.Collections.Specialized;
 using System.Diagnostics;
 using System.IO;
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using System.Security;
 using Microsoft.Build.Framework;
 
@@ -12,24 +13,38 @@ namespace GodotSharpTools.Build
     public class BuildInstance : IDisposable
     {
         [MethodImpl(MethodImplOptions.InternalCall)]
-        internal extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode);
+        private extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
-        internal extern static string godot_icall_BuildInstance_get_MSBuildPath();
+        private extern static MSBuildInfo godot_icall_BuildInstance_get_MSBuildInfo();
 
-        private static string MSBuildPath
+        [StructLayout(LayoutKind.Sequential)]
+        private struct MSBuildInfo
         {
-            get
-            {
-                string ret = godot_icall_BuildInstance_get_MSBuildPath();
+            string path;
+            string frameworkPathOverride;
 
-                if (ret == null)
-                    throw new FileNotFoundException("Cannot find the MSBuild executable.");
+            public string MSBuildPath
+            {
+                get { return path; }
+            }
 
-                return ret;
+            public string FrameworkPathOverride
+            {
+                get { return frameworkPathOverride; }
             }
         }
 
+        private static MSBuildInfo GetMSBuildInfo()
+        {
+            MSBuildInfo ret = godot_icall_BuildInstance_get_MSBuildInfo();
+
+            if (ret.MSBuildPath == null)
+                throw new FileNotFoundException("Cannot find the MSBuild executable.");
+
+            return ret;
+        }
+
         private string solution;
         private string config;
 
@@ -48,9 +63,19 @@ namespace GodotSharpTools.Build
 
         public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null)
         {
-            string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customProperties);
+            MSBuildInfo msbuildInfo = GetMSBuildInfo();
+
+            List<string> customPropertiesList = new List<string>();
+
+            if (customProperties != null)
+                customPropertiesList.AddRange(customProperties);
 
-            ProcessStartInfo startInfo = new ProcessStartInfo(MSBuildPath, compilerArgs);
+            if (msbuildInfo.FrameworkPathOverride.Length > 0)
+                customPropertiesList.Add("FrameworkPathOverride=" + msbuildInfo.FrameworkPathOverride);
+
+            string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList);
+
+            ProcessStartInfo startInfo = new ProcessStartInfo(msbuildInfo.MSBuildPath, compilerArgs);
 
             // No console output, thanks
             startInfo.RedirectStandardOutput = true;
@@ -82,9 +107,19 @@ namespace GodotSharpTools.Build
             if (process != null)
                 throw new InvalidOperationException("Already in use");
 
-            string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customProperties);
+            MSBuildInfo msbuildInfo = GetMSBuildInfo();
+
+            List<string> customPropertiesList = new List<string>();
+
+            if (customProperties != null)
+                customPropertiesList.AddRange(customProperties);
+
+            if (msbuildInfo.FrameworkPathOverride.Length > 0)
+                customPropertiesList.Add("FrameworkPathOverride=" + msbuildInfo.FrameworkPathOverride);
 
-            ProcessStartInfo startInfo = new ProcessStartInfo("msbuild", compilerArgs);
+            string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList);
+
+            ProcessStartInfo startInfo = new ProcessStartInfo(msbuildInfo.MSBuildPath, compilerArgs);
 
             // No console output, thanks
             startInfo.RedirectStandardOutput = true;
@@ -101,10 +136,13 @@ namespace GodotSharpTools.Build
 
             process.Start();
 
+            process.BeginOutputReadLine();
+            process.BeginErrorReadLine();
+
             return true;
         }
 
-        private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties)
+        private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, List<string> customProperties)
         {
             string arguments = string.Format(@"""{0}"" /v:normal /t:Build ""/p:{1}"" ""/l:{2},{3};{4}""",
                 solution,
@@ -114,12 +152,9 @@ namespace GodotSharpTools.Build
                 loggerOutputDir
             );
 
-            if (customProperties != null)
+            foreach (string customProperty in customProperties)
             {
-                foreach (string customProperty in customProperties)
-                {
-                    arguments += " /p:" + customProperty;
-                }
+                arguments += " \"/p:" + customProperty + "\"";
             }
 
             return arguments;

+ 39 - 22
modules/mono/editor/godotsharp_builds.cpp

@@ -71,10 +71,15 @@ String _find_build_engine_on_unix(const String &p_name) {
 }
 #endif
 
-MonoString *godot_icall_BuildInstance_get_MSBuildPath() {
+MonoString **godot_icall_BuildInstance_get_MSBuildInfo() {
 
 	GodotSharpBuilds::BuildTool build_tool = GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool")));
 
+	MonoString *res[2] = {
+		NULL, // MSBuildPath
+		NULL // FrameworkPathOverride
+	};
+
 #if defined(WINDOWS_ENABLED)
 	switch (build_tool) {
 		case GodotSharpBuilds::MSBUILD: {
@@ -84,11 +89,17 @@ MonoString *godot_icall_BuildInstance_get_MSBuildPath() {
 				if (!msbuild_tools_path.ends_with("\\"))
 					msbuild_tools_path += "\\";
 
-				return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe");
+				res[0] = GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe");
+
+				// FrameworkPathOverride
+				res[1] = GDMonoMarshal::mono_string_from_godot(GDMono::get_singleton()->get_mono_reg_info().assembly_dir);
+
+				return res;
 			}
 
-			OS::get_singleton()->print("Cannot find System's MSBuild. Trying with Mono's...\n");
-		}
+			if (OS::get_singleton()->is_stdout_verbose())
+				OS::get_singleton()->print("Cannot find System's MSBuild. Trying with Mono's...\n");
+		} // fall through
 		case GodotSharpBuilds::MSBUILD_MONO: {
 			String msbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("msbuild.bat");
 
@@ -96,17 +107,9 @@ MonoString *godot_icall_BuildInstance_get_MSBuildPath() {
 				WARN_PRINTS("Cannot find msbuild ('mono/builds/build_tool'). Tried with path: " + msbuild_path);
 			}
 
-			return GDMonoMarshal::mono_string_from_godot(msbuild_path);
-		}
-		case GodotSharpBuilds::XBUILD: {
-			String xbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("xbuild.bat");
-
-			if (!FileAccess::exists(xbuild_path)) {
-				WARN_PRINTS("Cannot find xbuild ('mono/builds/build_tool'). Tried with path: " + xbuild_path);
-			}
-
-			return GDMonoMarshal::mono_string_from_godot(xbuild_path);
-		}
+			res[0] = GDMonoMarshal::mono_string_from_godot(msbuild_path);
+			return res;
+		} break;
 		default:
 			ERR_EXPLAIN("You don't deserve to live");
 			CRASH_NOW();
@@ -118,25 +121,26 @@ MonoString *godot_icall_BuildInstance_get_MSBuildPath() {
 	if (build_tool != GodotSharpBuilds::XBUILD) {
 		if (msbuild_path.empty()) {
 			WARN_PRINT("Cannot find msbuild ('mono/builds/build_tool').");
-			return NULL;
+			return res;
 		}
 	} else {
 		if (xbuild_path.empty()) {
 			WARN_PRINT("Cannot find xbuild ('mono/builds/build_tool').");
-			return NULL;
+			return res;
 		}
 	}
 
-	return GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path);
+	res[0] = GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path);
+	return res;
 #else
-	return NULL;
+	return res;
 #endif
 }
 
 void GodotSharpBuilds::_register_internal_calls() {
 
 	mono_add_internal_call("GodotSharpTools.Build.BuildSystem::godot_icall_BuildInstance_ExitCallback", (void *)godot_icall_BuildInstance_ExitCallback);
-	mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildPath", (void *)godot_icall_BuildInstance_get_MSBuildPath);
+	mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildInfo", (void *)godot_icall_BuildInstance_get_MSBuildInfo);
 }
 
 void GodotSharpBuilds::show_build_error_dialog(const String &p_message) {
@@ -353,9 +357,22 @@ GodotSharpBuilds::GodotSharpBuilds() {
 	// Build tool settings
 	EditorSettings *ed_settings = EditorSettings::get_singleton();
 	if (!ed_settings->has_setting("mono/builds/build_tool")) {
-		ed_settings->set_setting("mono/builds/build_tool", MSBUILD);
+		ed_settings->set_setting("mono/builds/build_tool",
+#ifdef WINDOWS_ENABLED
+				// TODO: Default to MSBUILD_MONO if its csc.exe issue is fixed in the installed mono version
+				MSBUILD
+#else
+				MSBUILD_MONO
+#endif
+				);
 	}
-	ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM, "MSBuild (System),MSBuild (Mono),xbuild"));
+	ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM,
+#ifdef WINDOWS_ENABLED
+			"MSBuild (Mono),MSBuild (System)"
+#else
+			"MSBuild (Mono),xbuild (Deprecated)"
+#endif
+			));
 }
 
 GodotSharpBuilds::~GodotSharpBuilds() {

+ 5 - 2
modules/mono/editor/godotsharp_builds.h

@@ -67,9 +67,12 @@ public:
 	};
 
 	enum BuildTool {
-		MSBUILD,
 		MSBUILD_MONO,
-		XBUILD
+#ifdef WINDOWS_ENABLED
+		MSBUILD
+#else
+		XBUILD // Deprecated
+#endif
 	};
 
 	_FORCE_INLINE_ static GodotSharpBuilds *get_singleton() { return singleton; }

+ 73 - 19
modules/mono/mono_reg_utils.py

@@ -1,4 +1,5 @@
 import os
+import platform
 
 if os.name == 'nt':
     import sys
@@ -11,8 +12,7 @@ if os.name == 'nt':
 def _reg_open_key(key, subkey):
     try:
         return winreg.OpenKey(key, subkey)
-    except (WindowsError, EnvironmentError) as e:
-        import platform
+    except WindowsError, OSError:
         if platform.architecture()[0] == '32bit':
             bitness_sam = winreg.KEY_WOW64_64KEY
         else:
@@ -20,39 +20,93 @@ def _reg_open_key(key, subkey):
         return winreg.OpenKey(key, subkey, 0, winreg.KEY_READ | bitness_sam)
 
 
-def _find_mono_in_reg(subkey):
+def _reg_open_key_bits(key, subkey, bits):
+    sam = winreg.KEY_READ
+
+    if platform.architecture()[0] == '32bit':
+        if bits == '64':
+            # Force 32bit process to search in 64bit registry
+            sam |= winreg.KEY_WOW64_64KEY
+    else:
+        if bits == '32':
+            # Force 64bit process to search in 32bit registry
+            sam |= winreg.KEY_WOW64_32KEY
+
+    return winreg.OpenKey(key, subkey, 0, sam)
+
+
+def _find_mono_in_reg(subkey, bits):
     try:
-        with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey:
+        with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey:
             value, regtype = winreg.QueryValueEx(hKey, 'SdkInstallRoot')
             return value
-    except (WindowsError, EnvironmentError) as e:
+    except WindowsError, OSError:
         return None
 
-def _find_mono_in_reg_old(subkey):
+
+def _find_mono_in_reg_old(subkey, bits):
     try:
-        with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey:
+        with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey:
             default_clr, regtype = winreg.QueryValueEx(hKey, 'DefaultCLR')
             if default_clr:
-                return _find_mono_in_reg(subkey + '\\' + default_clr)
+                return _find_mono_in_reg(subkey + '\\' + default_clr, bits)
             return None
     except (WindowsError, EnvironmentError):
         return None
 
 
-def find_mono_root_dir():
-    dir = _find_mono_in_reg(r'SOFTWARE\Mono')
-    if dir:
-        return dir
-    dir = _find_mono_in_reg_old(r'SOFTWARE\Novell\Mono')
-    if dir:
-        return dir
-    return None
+def find_mono_root_dir(bits):
+    root_dir = _find_mono_in_reg(r'SOFTWARE\Mono', bits)
+    if root_dir is not None:
+        return root_dir
+    root_dir = _find_mono_in_reg_old(r'SOFTWARE\Novell\Mono', bits)
+    if root_dir is not None:
+        return root_dir
+    return ''
 
 
 def find_msbuild_tools_path_reg():
+    import subprocess
+
+    vswhere = os.getenv('PROGRAMFILES(X86)')
+    if not vswhere:
+        vswhere = os.getenv('PROGRAMFILES')
+    vswhere += r'\Microsoft Visual Studio\Installer\vswhere.exe'
+
+    vswhere_args = ['-latest', '-requires', 'Microsoft.Component.MSBuild']
+
     try:
-        with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0') as hKey:
+        lines = subprocess.check_output([vswhere] + vswhere_args).splitlines()
+
+        for line in lines:
+            parts = line.split(':', 1)
+
+            if len(parts) < 2 or parts[0] != 'installationPath':
+                continue
+
+            val = parts[1].strip()
+
+            if not val:
+                raise ValueError('Value of `installationPath` entry is empty')
+
+            return os.path.join(val, "MSBuild\\15.0\\Bin")
+
+        raise ValueError('Cannot find `installationPath` entry')
+    except ValueError as e:
+        print('Error reading output from vswhere: ' + e.message)
+    except WindowsError:
+        pass # Fine, vswhere not found
+    except subprocess.CalledProcessError, OSError:
+        pass
+
+    # Try to find 14.0 in the Registry
+
+    try:
+        subkey = r'SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0'
+        with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey:
             value, regtype = winreg.QueryValueEx(hKey, 'MSBuildToolsPath')
             return value
-    except (WindowsError, EnvironmentError) as e:
-        return None
+    except WindowsError, OSError:
+        return ''
+
+    return ''