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

Improve Visual Studio detection and add VS2019 support #1762

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 5 additions & 2 deletions gyp/pylib/gyp/generator/msvs.py
Expand Up @@ -88,6 +88,7 @@ def _import_OrderedDict():
'msvs_enable_winrt',
'msvs_requires_importlibrary',
'msvs_enable_winphone',
'msvs_enable_marmasm',
'msvs_application_type_revision',
'msvs_target_platform_version',
'msvs_target_platform_minversion',
Expand Down Expand Up @@ -3362,7 +3363,8 @@ def _GenerateMSBuildProject(project, options, version, generator_flags):
content += _GetMSBuildLocalProperties(project.msbuild_toolset)
content += import_cpp_props_section
content += import_masm_props_section
content += import_marmasm_props_section
if spec.get('msvs_enable_marmasm'):
content += import_marmasm_props_section
content += _GetMSBuildExtensions(props_files_of_rules)
content += _GetMSBuildPropertySheets(configurations)
content += macro_section
Expand All @@ -3375,7 +3377,8 @@ def _GenerateMSBuildProject(project, options, version, generator_flags):
content += _GetMSBuildProjectReferences(project)
content += import_cpp_targets_section
content += import_masm_targets_section
content += import_marmasm_targets_section
if spec.get('msvs_enable_marmasm'):
content += import_marmasm_targets_section
content += _GetMSBuildExtensionTargets(targets_files_of_rules)

if spec.get('msvs_external_builder'):
Expand Down
88 changes: 29 additions & 59 deletions lib/Find-VS2017.cs → lib/Find-VisualStudio.cs
Expand Up @@ -3,10 +3,13 @@
// See accompanying file LICENSE at https://github.com/node4good/windows-autoconf

// Usage:
// powershell -ExecutionPolicy Unrestricted -Version "2.0" -Command "&{Add-Type -Path Find-VS2017.cs; [VisualStudioConfiguration.Main]::Query()}"
// powershell -ExecutionPolicy Unrestricted -Command "Add-Type -Path Find-VisualStudio.cs; [VisualStudioConfiguration.Main]::PrintJson()"
// This script needs to be compatible with PowerShell v2 to run on Windows 2008R2 and Windows 7.

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections.Generic;

namespace VisualStudioConfiguration
{
Expand Down Expand Up @@ -184,90 +187,57 @@ public class SetupConfigurationClass

public static class Main
{
public static void Query()
public static void PrintJson()
{
ISetupConfiguration query = new SetupConfiguration();
ISetupConfiguration2 query2 = (ISetupConfiguration2)query;
IEnumSetupInstances e = query2.EnumAllInstances();

int pceltFetched;
ISetupInstance2[] rgelt = new ISetupInstance2[1];
StringBuilder log = new StringBuilder();
List<string> instances = new List<string>();
while (true)
{
e.Next(1, rgelt, out pceltFetched);
if (pceltFetched <= 0)
{
Console.WriteLine(String.Format("{{\"log\":\"{0}\"}}", log.ToString()));
Console.WriteLine(String.Format("[{0}]", string.Join(",", instances.ToArray())));
return;
}
if (CheckInstance(rgelt[0], ref log))
return;

instances.Add(InstanceJson(rgelt[0]));
}
}

private static bool CheckInstance(ISetupInstance2 setupInstance2, ref StringBuilder log)
private static string JsonString(string s)
{
// Visual Studio Community 2017 component directory:
// https://www.visualstudio.com/en-us/productinfo/vs2017-install-product-Community.workloads
return "\"" + s.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
}

string path = setupInstance2.GetInstallationPath().Replace("\\", "\\\\");
log.Append(String.Format("Found installation at: {0}\\n", path));
private static string InstanceJson(ISetupInstance2 setupInstance2)
{
// Visual Studio component directory:
// https://docs.microsoft.com/en-us/visualstudio/install/workload-and-component-ids

bool hasMSBuild = false;
bool hasVCTools = false;
uint Win10SDKVer = 0;
bool hasWin8SDK = false;
StringBuilder json = new StringBuilder();
json.Append("{");

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)) {
string[] parts = id.Substring(Win10SDKPrefix.Length).Split('.');
if (parts.Length > 1 && parts[1] != "Desktop")
continue;
uint foundSdkVer;
if (UInt32.TryParse(parts[0], out foundSdkVer))
Win10SDKVer = Math.Max(Win10SDKVer, foundSdkVer);
} else if (id == "Microsoft.VisualStudio.Component.Windows81SDK")
hasWin8SDK = true;
else
continue;

log.Append(String.Format(" - Found {0}\\n", id));
}
string path = JsonString(setupInstance2.GetInstallationPath());
json.Append(String.Format("\"path\":{0},", path));

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");
string version = JsonString(setupInstance2.GetInstallationVersion());
json.Append(String.Format("\"version\":{0},", version));

if (hasMSBuild && hasVCTools)
List<string> packages = new List<string>();
foreach (ISetupPackageReference package in setupInstance2.GetPackages())
{
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;
}
string id = JsonString(package.GetId());
packages.Add(id);
}
json.Append(String.Format("\"packages\":[{0}]", string.Join(",", packages.ToArray())));

log.Append(" - Some required components are missing, not using this installation\\n");
return false;
json.Append("}");
return json.ToString();
}
}
}
76 changes: 7 additions & 69 deletions lib/build.js
Expand Up @@ -6,7 +6,6 @@ var fs = require('graceful-fs')
, glob = require('glob')
, log = require('npmlog')
, which = require('which')
, exec = require('child_process').exec
, win = process.platform === 'win32'

exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module'
Expand Down Expand Up @@ -96,7 +95,11 @@ function build (gyp, argv, callback) {

function doWhich () {
// On Windows use msbuild provided by node-gyp configure
if (win && config.variables.msbuild_path) {
if (win) {
if (!config.variables.msbuild_path) {
return callback(new Error(
'MSBuild is not set, please run `node-gyp configure`.'))
}
command = config.variables.msbuild_path
log.verbose('using MSBuild:', command)
doBuild()
Expand All @@ -105,80 +108,15 @@ function build (gyp, argv, callback) {
// First make sure we have the build command in the PATH
which(command, function (err, execPath) {
if (err) {
if (win && /not found/.test(err.message)) {
// On windows and no 'msbuild' found. Let's guess where it is
findMsbuild()
} else {
// Some other error or 'make' not found on Unix, report that to the user
callback(err)
}
// Some other error or 'make' not found on Unix, report that to the user
callback(err)
return
}
log.verbose('`which` succeeded for `' + command + '`', execPath)
doBuild()
})
}

/**
* Search for the location of "msbuild.exe" file on Windows.
*/

function findMsbuild () {
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'
if (process.arch !== 'ia32')
cmd += ' /reg:32'
exec(cmd, function (err, stdout) {
if (err) {
return callback(new Error(err.message + '\n' + notfoundErr))
}
var reVers = /ToolsVersions\\([^\\]+)$/i
, rePath = /\r\n[ \t]+MSBuildToolsPath[ \t]+REG_SZ[ \t]+([^\r]+)/i
, msbuilds = []
, r
, msbuildPath
stdout.split('\r\n\r\n').forEach(function(l) {
if (!l) return
l = l.trim()
if (r = reVers.exec(l.substring(0, l.indexOf('\r\n')))) {
var ver = parseFloat(r[1], 10)
if (ver >= 3.5) {
if (r = rePath.exec(l)) {
msbuilds.push({
version: ver,
path: r[1]
})
}
}
}
})
msbuilds.sort(function (x, y) {
return (x.version < y.version ? -1 : 1)
})
;(function verifyMsbuild () {
if (!msbuilds.length) return callback(new Error(notfoundErr))
msbuildPath = path.resolve(msbuilds.pop().path, 'msbuild.exe')
fs.stat(msbuildPath, function (err) {
if (err) {
if (err.code == 'ENOENT') {
if (msbuilds.length) {
return verifyMsbuild()
} else {
callback(new Error(notfoundErr))
}
} else {
callback(err)
}
return
}
command = msbuildPath
doBuild()
})
})()
})
}

/**
* Actually spawn the process and compile the module.
*/
Expand Down
72 changes: 23 additions & 49 deletions lib/configure.js
Expand Up @@ -17,8 +17,9 @@ var fs = require('graceful-fs')
, win = process.platform === 'win32'
, findNodeDirectory = require('./find-node-directory')
, msgFormat = require('util').format
, logWithPrefix = require('./util').logWithPrefix
if (win)
var findVS2017 = require('./find-vs2017')
var findVisualStudio = require('./find-visualstudio')

exports.usage = 'Generates ' + (win ? 'MSVC project files' : 'a Makefile') + ' for the current module'

Expand Down Expand Up @@ -82,22 +83,16 @@ function configure (gyp, argv, callback) {
mkdirp(buildDir, function (err, isNew) {
if (err) return callback(err)
log.verbose('build dir', '"build" dir needed to be created?', isNew)
if (win && (!gyp.opts.msvs_version || gyp.opts.msvs_version === '2017')) {
findVS2017(function (err, vsSetup) {
if (err) {
log.verbose('Not using VS2017:', err.message)
createConfigFile()
} else {
createConfigFile(null, vsSetup)
}
})
if (win) {
findVisualStudio(release.semver, gyp.opts.msvs_version,
createConfigFile)
} else {
createConfigFile()
}
})
}

function createConfigFile (err, vsSetup) {
function createConfigFile (err, vsInfo) {
if (err) return callback(err)

var configFilename = 'config.gypi'
Expand Down Expand Up @@ -144,17 +139,23 @@ function configure (gyp, argv, callback) {
// disable -T "thin" static archives by default
variables.standalone_static_library = gyp.opts.thin ? 0 : 1

if (vsSetup) {
// GYP doesn't (yet) have support for VS2017, so we force it to VS2015
// to avoid pulling a floating patch that has not landed upstream.
// Ref: https://chromium-review.googlesource.com/#/c/433540/
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')
if (win) {
process.env['GYP_MSVS_VERSION'] = Math.min(vsInfo.versionYear, 2015)
process.env['GYP_MSVS_OVERRIDE_PATH'] = vsInfo.path
defaults['msbuild_toolset'] = vsInfo.toolset
if (vsInfo.sdk) {
defaults['msvs_windows_target_platform_version'] = vsInfo.sdk
}
if (variables.target_arch == 'arm64') {
if (vsInfo.versionMajor > 15 ||
(vsInfo.versionMajor === 15 && vsInfo.versionMajor >= 9)) {
defaults['msvs_enable_marmasm'] = 1
} else {
log.warn('Compiling ARM64 assembly is only available in\n' +
'Visual Studio 2017 version 15.9 and above')
}
}
variables['msbuild_path'] = vsInfo.msBuild
}

// loop through the rest of the opts and add the unknown ones as variables.
Expand Down Expand Up @@ -220,20 +221,6 @@ function configure (gyp, argv, callback) {
}
}

function hasMsvsVersion () {
return argv.some(function (arg) {
return arg.indexOf('msvs_version') === 0
})
}

if (win && !hasMsvsVersion()) {
if ('msvs_version' in gyp.opts) {
argv.push('-G', 'msvs_version=' + gyp.opts.msvs_version)
} else {
argv.push('-G', 'msvs_version=auto')
}
}

// include all the ".gypi" files that were found
configs.forEach(function (config) {
argv.push('-I', config)
Expand Down Expand Up @@ -644,16 +631,3 @@ function findPython (configPython, callback) {
var finder = new PythonFinder(configPython, callback)
finder.findPython()
}

function logWithPrefix (log, prefix) {
function setPrefix(logFunction) {
return (...args) => logFunction.apply(null, [prefix, ...args])
}
return {
silly: setPrefix(log.silly),
verbose: setPrefix(log.verbose),
info: setPrefix(log.info),
warn: setPrefix(log.warn),
error: setPrefix(log.error),
}
}