Skip to content

Commit

Permalink
win: find and setup for VS2017
Browse files Browse the repository at this point in the history
Reviewed-By: João Reis <reis@janeasystems.com>
  • Loading branch information
refack authored and joaocgreis committed Feb 27, 2017
1 parent ec5fc36 commit 1f79c98
Show file tree
Hide file tree
Showing 4 changed files with 336 additions and 4 deletions.
262 changes: 262 additions & 0 deletions lib/Find-VS2017.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
11 changes: 10 additions & 1 deletion lib/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -124,6 +126,13 @@ function build (gyp, argv, callback) {
*/

function findMsbuild () {
if (config.variables.msbuild_path) {
command = config.variables.msbuild_path
log.verbose('using MSBuild:', command)
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'
Expand Down
20 changes: 17 additions & 3 deletions lib/configure.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -137,6 +139,18 @@ 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
variables['msbuild_path'] = path.join(vsSetup.path, 'MSBuild', '15.0', 'Bin', 'MSBuild.exe')
}
}

// loop through the rest of the opts and add the unknown ones as variables.
// this allows for module-specific configure flags like:
//
Expand Down Expand Up @@ -317,9 +331,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++) {
Expand Down
47 changes: 47 additions & 0 deletions lib/find-vs2017.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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 csFile = path.join(__dirname, 'Find-VS2017.cs')
const psQuery = ps + '"&{Add-Type -Path \'' + csFile + '\'; [VisualStudioConfiguration.Main]::Query()}" 2>&1'

var vsSetup
try {
const vsSetupRaw = execSync(psQuery, { encoding: 'utf8' })
log.silly('find vs2017', 'vsSetupRaw:', vsSetupRaw)
vsSetup = JSON.parse(vsSetupRaw)
log.silly('find vs2017', 'vsSetup:', vsSetup)
} catch (e) {
log.silly('find vs2017', e)
log.verbose('find vs2017', 'could not use PowerShell to find VS2017')
return cache
}

if (vsSetup && vsSetup.log)
log.verbose('find vs2017', vsSetup.log.trimRight())

if (!vsSetup || !vsSetup.path || !vsSetup.sdk) {
log.verbose('find vs2017', 'no usable installation found')
return cache
}

cache = {
"path": vsSetup.path,
"sdk": vsSetup.sdk
}

log.verbose('find vs2017', 'using installation:', cache.path)
return cache
}

module.exports = findVS2017

0 comments on commit 1f79c98

Please sign in to comment.