diff --git a/src/Core/Toolchains/MSVCToolchain.cs b/src/Core/Toolchains/MSVCToolchain.cs new file mode 100644 index 00000000..09e3b30d --- /dev/null +++ b/src/Core/Toolchains/MSVCToolchain.cs @@ -0,0 +1,367 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using CppSharp.Parser; +using Microsoft.Win32; + +namespace CppSharp +{ + /// Represents a Visual Studio version. + public enum VisualStudioVersion + { + VS2012, + VS2013, + VS2015, + Latest + } + + /// Represents a toolchain with associated version and directory. + public struct ToolchainVersion + { + public float Version; + public string Directory; + + public override string ToString() + { + return string.Format("{0} (version: {1})", Directory, Version); + } + } + + public static class MSVCToolchain + { + static void DumpSdks(string sku, IEnumerable sdks) + { + Console.WriteLine("\n{0} SDKs:", sku); + foreach (var sdk in sdks) + Console.WriteLine("\t({0}) {1}", sdk.Version, sdk.Directory); + } + + /// Dumps the detected SDK versions. + public static void DumpSdks() + { + List vsSdks; + GetVisualStudioSdks(out vsSdks); + DumpSdks("Visual Studio", vsSdks); + + List windowsSdks; + GetWindowsSdks(out windowsSdks); + DumpSdks("Windows", windowsSdks); + + List netFrameworkSdks; + GetNetFrameworkSdks(out netFrameworkSdks); + DumpSdks(".NET Framework", netFrameworkSdks); + + List msbuildSdks; + GetMSBuildSdks(out msbuildSdks); + DumpSdks("MSBuild", msbuildSdks); + } + + /// Dumps include directories for selected toolchain. + public static void DumpSdkIncludes(VisualStudioVersion vsVersion = + VisualStudioVersion.Latest) + { + Console.WriteLine("\nInclude search path (VS: {0}):", vsVersion); + var includes = GetSystemIncludes(vsVersion); + foreach (var include in includes) + Console.WriteLine("\t{0}", include); + } + + static int GetVisualStudioVersion(VisualStudioVersion version) + { + switch (version) + { + case VisualStudioVersion.VS2012: + return 11; + case VisualStudioVersion.VS2013: + return 12; + case VisualStudioVersion.VS2015: + case VisualStudioVersion.Latest: + return 14; + default: + throw new ArgumentOutOfRangeException("Unknown Visual Studio version"); + } + } + + /// Gets the system include folders for the given Visual Studio version. + public static List GetSystemIncludes(VisualStudioVersion vsVersion) + { + var includes = new List(); + + List vsSdks; + MSVCToolchain.GetVisualStudioSdks(out vsSdks); + + if (vsSdks.Count == 0) + throw new Exception("Could not find a valid Visual Studio toolchain"); + + var vsSdk = (vsVersion == VisualStudioVersion.Latest) + ? vsSdks.Last() + : vsSdks.Find(version => + (int)version.Version == GetVisualStudioVersion(vsVersion)); + + var vsDir = vsSdk.Directory; + vsDir = vsDir.Substring(0, vsDir.LastIndexOf(@"\Common7\IDE", + StringComparison.Ordinal)); + + includes.Add(Path.Combine(vsDir, @"VC\include")); + + // Check VCVarsQueryRegistry.bat to see which Windows SDK version + // is supposed to be used with this VS version. + var vcVarsPath = Path.Combine(vsDir, @"Common7\Tools\VCVarsQueryRegistry.bat"); + + int windowsSdkMajorVer = 0, windowsSdkMinorVer = 0; + if (File.Exists(vcVarsPath)) + { + var vcVarsFile = File.ReadAllText(vcVarsPath); + var match = Regex.Match(vcVarsFile, @"Windows\\v([1-9][0-9]*)\.?([0-9]*)"); + if (match.Success) + { + windowsSdkMajorVer = int.Parse(match.Groups[1].Value); + windowsSdkMinorVer = int.Parse(match.Groups[2].Value); + } + } + + List windowSdks; + MSVCToolchain.GetWindowsSdks(out windowSdks); + + var windowSdk = (windowsSdkMajorVer != 0) + ? windowSdks.Find(version => + (int)Math.Floor(version.Version) == windowsSdkMajorVer) + : windowSdks.Last(); + + var windowSdkDir = windowSdk.Directory; + + // Older Visual Studio versions provide their own Windows SDK. + if (windowSdks.Count == 0) + { + includes.Add(Path.Combine(vsDir, @"\VC\PlatformSDK\Include")); + } + else + { + if (windowsSdkMajorVer >= 8) + { + includes.Add(Path.Combine(windowSdkDir, @"include\shared")); + includes.Add(Path.Combine(windowSdkDir, @"include\um")); + includes.Add(Path.Combine(windowSdkDir, @"include\winrt")); + } + else + { + includes.Add(Path.Combine(windowSdkDir, "include")); + } + } + + return includes; + } + + /// Gets .NET framework installation directories. + public static bool GetNetFrameworkSdks(out List versions) + { + versions = new List(); + + GetToolchainsFromSystemRegistry( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\.NETFramework", + "InstallRoot", versions, RegistryView.Registry32); + + if (versions.Count == 0 && Environment.Is64BitProcess) + { + GetToolchainsFromSystemRegistry( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\.NETFramework", + "InstallRoot", versions, RegistryView.Registry64); + } + + versions.Sort((v1, v2) => (int)(v1.Version - v2.Version)); + + return versions.Count != 0; + } + + /// Gets MSBuild installation directories. + public static bool GetMSBuildSdks(out List versions) + { + versions = new List(); + + GetToolchainsFromSystemRegistry( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions", + "MSBuildToolsPath", versions, RegistryView.Registry32); + + if (versions.Count == 0 && Environment.Is64BitProcess) + { + GetToolchainsFromSystemRegistry( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions", + "MSBuildToolsPath", versions, RegistryView.Registry64); + } + + versions.Sort((v1, v2) => (int)(v1.Version - v2.Version)); + + return versions.Count != 0; + } + + /// Gets Windows SDK installation directories. + public static bool GetWindowsSdks(out List versions) + { + versions = new List(); + + GetToolchainsFromSystemRegistry( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows", + "InstallationFolder", versions, RegistryView.Registry32); + + if (versions.Count == 0 && Environment.Is64BitProcess) + { + GetToolchainsFromSystemRegistry( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows", + "InstallationFolder", versions, RegistryView.Registry64); + } + + versions.Sort((v1, v2) => (int)(v1.Version - v2.Version)); + + return versions.Count != 0; + } + + /// Gets Visual Studio installation directories. + public static bool GetVisualStudioSdks(out List versions) + { + versions = new List(); + + GetToolchainsFromSystemRegistry( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio", + "InstallDir", versions, RegistryView.Registry32); + + if (versions.Count == 0 && Environment.Is64BitProcess) + { + GetToolchainsFromSystemRegistry( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio", + "InstallDir", versions, RegistryView.Registry64); + } + + GetToolchainsFromSystemRegistry( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VCExpress", + "InstallDir", versions, RegistryView.Registry32); + + if (versions.Count == 0 && Environment.Is64BitProcess) + { + GetToolchainsFromSystemRegistry( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VCExpress", + "InstallDir", versions, RegistryView.Registry64); + } + + versions.Sort((v1, v2) => (int)(v1.Version - v2.Version)); + + return true; + } + + /// Read registry string. + /// This also supports a means to look for high-versioned keys by use + /// of a $VERSION placeholder in the key path. + /// $VERSION in the key path is a placeholder for the version number, + /// causing the highest value path to be searched for and used. + /// I.e. "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio". + /// There can be additional characters in the component. Only the numeric + /// characters are compared. + static bool GetToolchainsFromSystemRegistry(string keyPath, string valueName, + ICollection entries, RegistryView view) + { + string subKey; + var hive = GetRegistryHive(keyPath, out subKey); + using (var rootKey = RegistryKey.OpenBaseKey(hive, view)) + { + using (var key = rootKey.OpenSubKey(subKey, writable: false)) + { + if (key == null) + return false; + + foreach (var subKeyName in key.GetSubKeyNames()) + { + ToolchainVersion entry; + if (HandleToolchainRegistrySubKey(out entry, key, valueName, + subKeyName)) + entries.Add(entry); + } + } + } + + return true; + } + + static bool HandleToolchainRegistrySubKey(out ToolchainVersion entry, + RegistryKey key, string valueName, string subKeyName) + { + entry = new ToolchainVersion(); + + // Get the number version from the key. + var match = Regex.Match(subKeyName, @"[1-9][0-9]*\.?[0-9]*"); + if (!match.Success) + return false; + + var versionText = match.Groups[0].Value; + + float version; + float.TryParse(versionText, NumberStyles.Number, + CultureInfo.InvariantCulture, out version); + + using (var versionKey = key.OpenSubKey(subKeyName)) + { + if (versionKey == null) + return false; + + // Check that the key has a value passed by the caller. + var keyValue = versionKey.GetValue(valueName); + if (keyValue == null) + return false; + + entry = new ToolchainVersion + { + Version = version, + Directory = keyValue.ToString() + }; + } + + return true; + } + + static RegistryHive GetRegistryHive(string keyPath, out string subKey) + { + var hive = (RegistryHive)0; + subKey = null; + + if (keyPath.StartsWith("HKEY_CLASSES_ROOT\\")) + { + hive = RegistryHive.ClassesRoot; + subKey = keyPath.Substring(18); + } + else if (keyPath.StartsWith("HKEY_USERS\\")) + { + hive = RegistryHive.Users; + subKey = keyPath.Substring(11); + } + else if (keyPath.StartsWith("HKEY_LOCAL_MACHINE\\")) + { + hive = RegistryHive.LocalMachine; + subKey = keyPath.Substring(19); + } + else if (keyPath.StartsWith("HKEY_CURRENT_USER\\")) + { + hive = RegistryHive.CurrentUser; + subKey = keyPath.Substring(18); + } + + return hive; + } + } + + public static partial class OptionsExtensions + { + /// Sets up the parser options to work with the given Visual Studio toolchain. + public static void SetupMSVC(this ParserOptions options, + VisualStudioVersion vsVersion = VisualStudioVersion.Latest) + { + options.MicrosoftMode = true; + options.NoBuiltinIncludes = true; + options.NoStandardIncludes = true; + + var includes = MSVCToolchain.GetSystemIncludes(vsVersion); + foreach (var include in includes) + options.addSystemIncludeDirs(include); + } + } +}