From aa8626e7968a95f3b14345593d610d3da5cf38c4 Mon Sep 17 00:00:00 2001 From: Refael Ackermann Date: Mon, 20 Feb 2017 19:16:24 -0500 Subject: [PATCH] win: find and setup for VS2017 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-By: João Reis --- lib/Find-VS2017.cs | 262 +++++++++++++++++++++++++++++++++++++++++++++ lib/build.js | 12 ++- lib/configure.js | 19 +++- lib/find-vs2017.js | 56 ++++++++++ 4 files changed, 345 insertions(+), 4 deletions(-) create mode 100644 lib/Find-VS2017.cs create mode 100644 lib/find-vs2017.js diff --git a/lib/Find-VS2017.cs b/lib/Find-VS2017.cs new file mode 100644 index 0000000000..fe0d1e08e1 --- /dev/null +++ b/lib/Find-VS2017.cs @@ -0,0 +1,262 @@ +// powershell -ExecutionPolicy Unrestricted -Version "2.0" -Command "&{Add-Type -Path Find-VS2017.cs; [VisualStudioConfiguration.Main]::Query()}" +using System; +using System.Text; +using System.Runtime.InteropServices; + +namespace VisualStudioConfiguration +{ + [Flags] + public enum InstanceState : uint + { + None = 0, + Local = 1, + Registered = 2, + NoRebootRequired = 4, + NoErrors = 8, + Complete = 4294967295, + } + + [Guid("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface IEnumSetupInstances + { + + void Next([MarshalAs(UnmanagedType.U4), In] int celt, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface), Out] ISetupInstance[] rgelt, + [MarshalAs(UnmanagedType.U4)] out int pceltFetched); + + void Skip([MarshalAs(UnmanagedType.U4), In] int celt); + + void Reset(); + + [return: MarshalAs(UnmanagedType.Interface)] + IEnumSetupInstances Clone(); + } + + [Guid("42843719-DB4C-46C2-8E7C-64F1816EFD5B")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupConfiguration + { + } + + [Guid("26AAB78C-4A60-49D6-AF3B-3C35BC93365D")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupConfiguration2 : ISetupConfiguration + { + + [return: MarshalAs(UnmanagedType.Interface)] + IEnumSetupInstances EnumInstances(); + + [return: MarshalAs(UnmanagedType.Interface)] + ISetupInstance GetInstanceForCurrentProcess(); + + [return: MarshalAs(UnmanagedType.Interface)] + ISetupInstance GetInstanceForPath([MarshalAs(UnmanagedType.LPWStr), In] string path); + + [return: MarshalAs(UnmanagedType.Interface)] + IEnumSetupInstances EnumAllInstances(); + } + + [Guid("B41463C3-8866-43B5-BC33-2B0676F7F42E")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupInstance + { + } + + [Guid("89143C9A-05AF-49B0-B717-72E218A2185C")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupInstance2 : ISetupInstance + { + [return: MarshalAs(UnmanagedType.BStr)] + string GetInstanceId(); + + [return: MarshalAs(UnmanagedType.Struct)] + System.Runtime.InteropServices.ComTypes.FILETIME GetInstallDate(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetInstallationName(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetInstallationPath(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetInstallationVersion(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetDisplayName([MarshalAs(UnmanagedType.U4), In] int lcid); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetDescription([MarshalAs(UnmanagedType.U4), In] int lcid); + + [return: MarshalAs(UnmanagedType.BStr)] + string ResolvePath([MarshalAs(UnmanagedType.LPWStr), In] string pwszRelativePath); + + [return: MarshalAs(UnmanagedType.U4)] + InstanceState GetState(); + + [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UNKNOWN)] + ISetupPackageReference[] GetPackages(); + + ISetupPackageReference GetProduct(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetProductPath(); + + [return: MarshalAs(UnmanagedType.VariantBool)] + bool IsLaunchable(); + + [return: MarshalAs(UnmanagedType.VariantBool)] + bool IsComplete(); + + ISetupPropertyStore GetProperties(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetEnginePath(); + } + + [Guid("DA8D8A16-B2B6-4487-A2F1-594CCCCD6BF5")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupPackageReference + { + + [return: MarshalAs(UnmanagedType.BStr)] + string GetId(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetVersion(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetChip(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetLanguage(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetBranch(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetType(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetUniqueId(); + + [return: MarshalAs(UnmanagedType.VariantBool)] + bool GetIsExtension(); + } + + [Guid("c601c175-a3be-44bc-91f6-4568d230fc83")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupPropertyStore + { + + [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] + string[] GetNames(); + + object GetValue([MarshalAs(UnmanagedType.LPWStr), In] string pwszName); + } + + [Guid("42843719-DB4C-46C2-8E7C-64F1816EFD5B")] + [CoClass(typeof(SetupConfigurationClass))] + [ComImport] + public interface SetupConfiguration : ISetupConfiguration2, ISetupConfiguration + { + } + + [Guid("177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D")] + [ClassInterface(ClassInterfaceType.None)] + [ComImport] + public class SetupConfigurationClass + { + } + + public static class Main + { + public static void Query() + { + ISetupConfiguration query = new SetupConfiguration(); + ISetupConfiguration2 query2 = (ISetupConfiguration2)query; + IEnumSetupInstances e = query2.EnumAllInstances(); + + int pceltFetched; + ISetupInstance2[] rgelt = new ISetupInstance2[1]; + StringBuilder log = new StringBuilder(); + while (true) + { + e.Next(1, rgelt, out pceltFetched); + if (pceltFetched <= 0) + { + Console.WriteLine(String.Format("{{\"log\":\"{0}\"}}", log.ToString())); + return; + } + if (CheckInstance(rgelt[0], ref log)) + return; + } + } + + private static bool CheckInstance(ISetupInstance2 setupInstance2, ref StringBuilder log) + { + // Visual Studio Community 2017 component directory: + // https://www.visualstudio.com/en-us/productinfo/vs2017-install-product-Community.workloads + + string path = setupInstance2.GetInstallationPath().Replace("\\", "\\\\"); + log.Append(String.Format("Found installation at: {0}\\n", path)); + + bool hasMSBuild = false; + bool hasVCTools = false; + uint Win10SDKVer = 0; + bool hasWin8SDK = false; + + foreach (ISetupPackageReference package in setupInstance2.GetPackages()) + { + const string Win10SDKPrefix = "Microsoft.VisualStudio.Component.Windows10SDK."; + + string id = package.GetId(); + if (id == "Microsoft.Component.MSBuild") + hasMSBuild = true; + else if (id == "Microsoft.VisualStudio.Component.VC.Tools.x86.x64") + hasVCTools = true; + else if (id.StartsWith(Win10SDKPrefix)) + Win10SDKVer = Math.Max(Win10SDKVer, UInt32.Parse(id.Substring(Win10SDKPrefix.Length))); + else if (id == "Microsoft.VisualStudio.Component.Windows81SDK") + hasWin8SDK = true; + else + continue; + + log.Append(String.Format(" - Found {0}\\n", id)); + } + + if (!hasMSBuild) + log.Append(" - Missing MSBuild (Microsoft.Component.MSBuild)\\n"); + if (!hasVCTools) + log.Append(" - Missing VC++ 2017 v141 toolset (x86,x64) (Microsoft.VisualStudio.Component.VC.Tools.x86.x64)\\n"); + if ((Win10SDKVer == 0) && (!hasWin8SDK)) + log.Append(" - Missing a Windows SDK (Microsoft.VisualStudio.Component.Windows10SDK.* or Microsoft.VisualStudio.Component.Windows81SDK)\\n"); + + if (hasMSBuild && hasVCTools) + { + if (Win10SDKVer > 0) + { + log.Append(" - Using this installation with Windows 10 SDK"/*\\n*/); + Console.WriteLine(String.Format("{{\"log\":\"{0}\",\"path\":\"{1}\",\"sdk\":\"10.0.{2}.0\"}}", log.ToString(), path, Win10SDKVer)); + return true; + } + else if (hasWin8SDK) + { + log.Append(" - Using this installation with Windows 8.1 SDK"/*\\n*/); + Console.WriteLine(String.Format("{{\"log\":\"{0}\",\"path\":\"{1}\",\"sdk\":\"8.1\"}}", log.ToString(), path)); + return true; + } + } + + log.Append(" - Some required components are missing, not using this installation\\n"); + return false; + } + } +} diff --git a/lib/build.js b/lib/build.js index 0374fbc810..c5379be62a 100644 --- a/lib/build.js +++ b/lib/build.js @@ -14,7 +14,9 @@ var fs = require('graceful-fs') , mkdirp = require('mkdirp') , exec = require('child_process').exec , processRelease = require('./process-release') - , win = process.platform == 'win32' + , win = process.platform === 'win32' +if (win) + var findVS2017 = require('./find-vs2017') exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module' @@ -124,6 +126,14 @@ function build (gyp, argv, callback) { */ function findMsbuild () { + log.verbose('could not find "msbuild.exe" in PATH - trying VS2017 installer') + const vsSetup = findVS2017() + if (vsSetup) { + command = path.join(vsSetup.path, 'MSBuild', '15.0', 'Bin', 'MSBuild.exe') + copyNodeLib() + return + } + log.verbose('could not find "msbuild.exe" in PATH - finding location in registry') var notfoundErr = 'Can\'t find "msbuild.exe". Do you have Microsoft Visual Studio C++ 2008+ installed?' var cmd = 'reg query "HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions" /s' diff --git a/lib/configure.js b/lib/configure.js index d52b2902b0..c4da6e7309 100644 --- a/lib/configure.js +++ b/lib/configure.js @@ -19,9 +19,11 @@ var fs = require('graceful-fs') , cp = require('child_process') , extend = require('util')._extend , processRelease = require('./process-release') - , win = process.platform == 'win32' + , win = process.platform === 'win32' , findNodeDirectory = require('./find-node-directory') , msgFormat = require('util').format +if (win) + var findVS2017 = require('./find-vs2017') exports.usage = 'Generates ' + (win ? 'MSVC project files' : 'a Makefile') + ' for the current module' @@ -137,6 +139,17 @@ function configure (gyp, argv, callback) { // disable -T "thin" static archives by default variables.standalone_static_library = gyp.opts.thin ? 0 : 1 + if (win && !(gyp.opts.msvs_version && gyp.opts.msvs_version !== '2017')) { + const vsSetup = findVS2017() + if (vsSetup) { + gyp.opts.msvs_version = '2015' + process.env['GYP_MSVS_VERSION'] = 2015 + process.env['GYP_MSVS_OVERRIDE_PATH'] = vsSetup.path + defaults['msbuild_toolset'] = 'v141' + defaults['msvs_windows_target_platform_version'] = vsSetup.sdk + } + } + // loop through the rest of the opts and add the unknown ones as variables. // this allows for module-specific configure flags like: // @@ -317,9 +330,9 @@ function configure (gyp, argv, callback) { } /** - * Returns the first file or directory from an array of candidates that is + * Returns the first file or directory from an array of candidates that is * readable by the current user, or undefined if none of the candidates are - * readable. + * readable. */ function findAccessibleSync (logprefix, dir, candidates) { for (var next = 0; next < candidates.length; next++) { diff --git a/lib/find-vs2017.js b/lib/find-vs2017.js new file mode 100644 index 0000000000..8f4d91c24b --- /dev/null +++ b/lib/find-vs2017.js @@ -0,0 +1,56 @@ +const log = require('npmlog') + , execSync = require('child_process').execSync + , path = require('path') + +var hasCache = false + , cache = null + +function findVS2017() { + if (hasCache) + return cache + + hasCache = true + + const ps = 'PowerShell -ExecutionPolicy Unrestricted -Command ' + const psClsidTest = ps + 'Test-Path \'Registry::HKCR\\CLSID\\{177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D}\' 2>&1' + + try { + const hasClsidRaw = execSync(psClsidTest, { encoding: 'utf8' }) + log.silly('find vs2017', 'hasClsidRaw:', hasClsidRaw) + const hasClsid = hasClsidRaw.trim() === 'True' + if (!hasClsid) + log.verbose('find vs2017', 'could not find VS2017 installer class in registry') + } catch (e) { + log.verbose('find vs2017', e) + } + + const csFile = path.join(__dirname, 'Find-VS2017.cs') + const psQuery = ps + '"&{Add-Type -Path \'' + csFile + '\'; [VisualStudioConfiguration.Main]::Query()}" 2>&1' + + try { + const vsSetupRaw = execSync(psQuery, { encoding: 'utf8' }) + log.silly('find vs2017', 'vsSetupRaw:', vsSetupRaw) + } catch (e) { + log.verbose('find vs2017', e) + return cache + } + + try { + const vsSetup = JSON.parse(vsSetupRaw) + log.silly('find vs2017', 'vsSetup:', vsSetup) + } catch (e) { + log.verbose('find vs2017', e) + return cache + } + + log.verbose('find vs2017', vsSetup.log) + + cache = { + "path": vsSetup.path, + "sdk": vsSetup.sdk + } + + return cache +} + +module.exports = findVS2017