Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

win: find and setup for VS2017 #1130

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this file need source attribution and/or copyright? or is it ours?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was submitted by @refack in #1103 , I believe it was adapted from the python COM API by @bzoz in #1101 , which in turn got the GUIDs and API from the nuget package upstream. @refack can you confim?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote it myself. GUIDs extracted from interop

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add

// Copyright 2017 - Refael Ackermann
// Distributed under MIT style license
// See accompanying file LICENSE at https://github.com/node4good/windows-autoconf

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.VisualStudio.VC.MSBuild.Base")
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)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug found with new SDK #1144

else if (id == "Microsoft.VisualStudio.Component.Windows81SDK")
hasWin8SDK = true;
else
continue;

log.Append(String.Format(" - Found {0}\\n", id));
}

if (!hasMSBuild)
log.Append(" - Missing Visual Studio C++ core features (Microsoft.VisualStudio.VC.MSBuild.Base)\\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;
}
}
}
9 changes: 8 additions & 1 deletion lib/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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'

exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module'

Expand Down Expand Up @@ -124,6 +124,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')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you mind inverting this? reads really badly. (win && (!gyp.opts.msvs_version || gyp.opts.msvs_version === '2017'))

const vsSetup = findVS2017()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/const/var

if (vsSetup) {
gyp.opts.msvs_version = '2015'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we forcing this to make GYP happy, or is this (and the next line) a mistake?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are forcing this to make GYP happy, and to avoid pulling https://chromium-review.googlesource.com/#/c/433540/ (which did not land yet) as a floating patch, since floating patches seem to be avoided here.

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')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you wrap this at 80 columns?

}
}

// 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' })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var for consistency, also given that node-gyp still works for very old versions of Node

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I mean, s/var/const in this entire file

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