From 9f469887fc72c98f4a5fac736860546cf54f87ee Mon Sep 17 00:00:00 2001 From: Ignacio Etcheverry Date: Tue, 24 Oct 2017 22:47:27 +0200 Subject: [PATCH] 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. --- modules/mono/SCsub | 141 ++++++++++++++---- modules/mono/config.py | 80 +++++++--- .../GodotSharpTools/Build/BuildSystem.cs | 75 +++++++--- modules/mono/editor/godotsharp_builds.cpp | 61 +++++--- modules/mono/editor/godotsharp_builds.h | 7 +- modules/mono/mono_reg_utils.py | 92 +++++++++--- 6 files changed, 341 insertions(+), 115 deletions(-) diff --git a/modules/mono/SCsub b/modules/mono/SCsub index caf4fdb3cab..13212636e3f 100644 --- a/modules/mono/SCsub +++ b/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): - 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') - else: - msbuild_path = 'msbuild' + import subprocess + import mono_reg_utils as monoreg + from shutil import copyfile - output_path = os.path.abspath(os.path.join(str(target[0]), os.pardir)) + framework_path_override = '' + + if os.name == 'nt': + 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 = 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) + + 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' ] + try: + subprocess.check_call(msbuild_args, env=msbuild_env) + except subprocess.CalledProcessError: + raise RuntimeError('GodotSharpTools build failed') - 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') + 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' diff --git a/modules/mono/config.py b/modules/mono/config.py index 0833d30ce17..44eef45f76f 100644 --- a/modules/mono/config.py +++ b/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 mono_dll_name: + raise RuntimeError('Could not find mono shared library in: ' + mono_bin_path) - if not os.path.isdir('bin'): - os.mkdir('bin') - elif os.path.exists(mono_dll_dst): - copy_mono_dll = False - - 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') diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs index 5544233eb7e..bdbef82aad1 100644 --- a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs +++ b/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,22 +13,36 @@ 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 path; + string frameworkPathOverride; + + public string MSBuildPath { - string ret = godot_icall_BuildInstance_get_MSBuildPath(); - - if (ret == null) - throw new FileNotFoundException("Cannot find the MSBuild executable."); - - return ret; + get { return path; } } + + 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; @@ -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(); - ProcessStartInfo startInfo = new ProcessStartInfo(MSBuildPath, compilerArgs); + List customPropertiesList = new List(); + + if (customProperties != null) + customPropertiesList.AddRange(customProperties); + + 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(); - ProcessStartInfo startInfo = new ProcessStartInfo("msbuild", compilerArgs); + List customPropertiesList = new List(); + + if (customProperties != null) + customPropertiesList.AddRange(customProperties); + + 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; @@ -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 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; diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp index d3808769fb2..c3b9c140e4b 100644 --- a/modules/mono/editor/godotsharp_builds.cpp +++ b/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() { diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h index 6d5fa3b44a6..3486f9f3e9f 100644 --- a/modules/mono/editor/godotsharp_builds.h +++ b/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; } diff --git a/modules/mono/mono_reg_utils.py b/modules/mono/mono_reg_utils.py index e9988625f52..22c4aeaf4ce 100644 --- a/modules/mono/mono_reg_utils.py +++ b/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 ''