From d44e5b995b1261da2664ce464f958bbc58f72a10 Mon Sep 17 00:00:00 2001 From: Refael Ackermann Date: Tue, 10 Jan 2017 17:52:13 -0500 Subject: [PATCH] win: detect VS2017 & refactor envfile generation and harden ninja's `cmd` wrap mechanism --- lib/gyp.js | 3 +- lib/gyp/bindings.js | 15 ++++ lib/gyp/generator/ninja/index.js | 29 +++--- lib/gyp/platform/win.js | 149 +++++-------------------------- package.json | 1 + 5 files changed, 58 insertions(+), 139 deletions(-) diff --git a/lib/gyp.js b/lib/gyp.js index 623bd7c..d111cc2 100644 --- a/lib/gyp.js +++ b/lib/gyp.js @@ -358,7 +358,8 @@ gyp.main = function main(args, extra) { gyp_binary: args[0], home_dot_gyp: homeDotGyp, root_targets: options.root_targets, - target_arch: cmdlineDefaultVariables['target_arch'] || '' + target_arch: cmdlineDefaultVariables['target_arch'] || + gyp.bindings.process.arch }; // Start with the default variables from the command line. diff --git a/lib/gyp/bindings.js b/lib/gyp/bindings.js index 822e5f8..eff4bb7 100644 --- a/lib/gyp/bindings.js +++ b/lib/gyp/bindings.js @@ -32,6 +32,7 @@ exports.process = { env: process.env, cwd: () => process.cwd(), platform: process.platform, + arch: process.arch, exit: (code) => process.exit(code) }; @@ -42,3 +43,17 @@ exports.log = function log(message) { exports.error = function error(message) { process.stderr.write(message + '\n'); }; + +Object.defineProperty(exports, 'win', { + get: function get() { + // ====== a late require ======== + const getter = require('windows-autoconf'); + // We just need all the binding to be set on `exports` + getter.setBindings(exports); + return { + getMSVSVersion: getter.getMSVSVersion, + getOSBits: getter.getOSBits, + resolveDevEnvironment: getter.resolveDevEnvironment + }; + } +}); diff --git a/lib/gyp/generator/ninja/index.js b/lib/gyp/generator/ninja/index.js index 8fc39a2..b8c2146 100644 --- a/lib/gyp/generator/ninja/index.js +++ b/lib/gyp/generator/ninja/index.js @@ -70,8 +70,8 @@ function calculateVariables(defaultVariables) { defaultVariables['STATIC_LIB_SUFFIX'] = '.lib'; defaultVariables['SHARED_LIB_PREFIX'] = ''; defaultVariables['SHARED_LIB_SUFFIX'] = '.dll'; - defaultVariables['MSVS_VERSION'] = gyp.platform.win.getMSVSVersion(); - defaultVariables['MSVS_OS_BITS'] = gyp.platform.win.getOSBits(); + defaultVariables['MSVS_VERSION'] = gyp.bindings.win.getMSVSVersion(); + defaultVariables['MSVS_OS_BITS'] = gyp.bindings.win.getOSBits(); } else { // On Solaris NODE_PLATFORM is `sunos`, while GYP's `OS` variable should be // `solaris` @@ -128,6 +128,7 @@ function Ninja(options) { this.bashAnd = this.flavor === 'win32' ? '&' : '&&'; this.actionNames = options.actionNames; + this.targetArch = options.targetArch; } Ninja.prototype.expand = function expand(p, productDir) { @@ -352,8 +353,8 @@ Ninja.prototype.actionCmd = function actionCmd(base, toBase, cmds) { if (this.flavor !== 'win32') return res; - // TODO(indutny): escape quotes in res - return `cmd.exe /s /c "${res}"`; + const nw = gyp.platform.win.getNinjaWrapper(this.targetArch); + return `${nw} cmd.exe /s /c "${res}"`; }; Ninja.prototype.copies = function copies() { @@ -417,7 +418,9 @@ Ninja.prototype.actions = function actions() { res = res.concat(outputs); if (action.process_outputs_as_sources === '1') { - this.targetDict.sources = (this.targetDict.sources || []).concat(action.outputs); + let trg = this.targetDict; + trg.sources = trg.sources || []; + trg.sources = trg.sources.concat(action.outputs); } this.n.build(actionRule, outputs, inputs, { @@ -684,17 +687,20 @@ NinjaMain.prototype.rulesAndTargets = function rulesAndTargets() { let useCxx = false; const ninjas = this.ninjas; const actionNames = {}; + const targetArch = this.params['target_arch']; const ninjaList = this.targetList.map((target, index) => { + const targetDict = this.targetDicts[target].configurations[this.config]; const ninja = new Ninja({ - index, + index: index, outDir: this.outDir, configDir: this.configDir, topDir: this.topDir, - target, - targetDict: this.targetDicts[target].configurations[this.config], - ninjas, + target: target, + targetDict: targetDict, + ninjas: ninjas, config: this.config, - actionNames + actionNames: actionNames, + targetArch: targetArch }); ninjas[target] = ninja; return ninja; @@ -707,8 +713,7 @@ NinjaMain.prototype.rulesAndTargets = function rulesAndTargets() { }); if (process.platform === 'win32') { - gyp.platform.win.ninjaRules(main, this.configDir, - this.options.generator_flags, this.params); + gyp.platform.win.ninjaRules(main, this.configDir, this.params); } else { gyp.platform.unix.ninjaRules(main, useCxx); } diff --git a/lib/gyp/platform/win.js b/lib/gyp/platform/win.js index b6491c7..bebed71 100644 --- a/lib/gyp/platform/win.js +++ b/lib/gyp/platform/win.js @@ -3,27 +3,25 @@ const gyp = require('../../gyp'); const fs = gyp.bindings.fs; const path = gyp.bindings.path; -const process = gyp.bindings.process; const win = exports; -win.ninjaRules = function ninjaRules(n, outDir, generatorFlags, params) { - let envFile = win.genEnvironment( - outDir, - generatorFlags['msvs_version'] || 'auto', - params['target_arch'] || 'ia32'); - if (envFile) - envFile = ` -e ${envFile} `; - else - envFile = ''; +win.getEnvFileName = function getEnvFileName(target_arch) { + return `environment.${target_arch}`; +}; - const ninjaWrap = `ninja -t msvc ${envFile}--`; +win.getNinjaWrapper = function getNinjaWrapper(target_arch) { + const envFile = win.getEnvFileName(target_arch); + return `ninja -t msvc -e ${envFile} --`; +}; +win.ninjaRules = function ninjaRules(n, outDir, params) { + win.genEnvironment(outDir, params['target_arch']); + const nw = this.getNinjaWrapper(params['target_arch']); n.rule('cc', { deps: 'msvc', // TODO(indutny): is /Fd$pdbname_c needed here? - command: `${ninjaWrap} $cc /nologo /showIncludes /FC ` + - '@$out.rsp /c $in /Fo$out', + command: `${nw} $cc /nologo /showIncludes /FC @$out.rsp /c $in /Fo$out`, rspfile: '$out.rsp', rspfile_content: '$defines $includes $cflags $cflags_c', description: 'CC $out' @@ -32,22 +30,21 @@ win.ninjaRules = function ninjaRules(n, outDir, generatorFlags, params) { n.rule('cxx', { deps: 'msvc', // TODO(indutny): is /Fd$pdbname_c needed here? - command: `${ninjaWrap} $cxx /nologo /showIncludes /FC ` + - '@$out.rsp /c $in /Fo$out', + command: `${nw} $cxx /nologo /showIncludes /FC @$out.rsp /c $in /Fo$out`, rspfile: '$out.rsp', rspfile_content: '$defines $includes $cflags $cflags_cc', description: 'CXX $out' }); n.rule('asm', { - command: `${ninjaWrap} $asm @$out.rsp /nologo /c /Fo $out $in`, + command: `${nw} $asm @$out.rsp /nologo /c /Fo $out $in`, rspfile: '$out.rsp', rspfile_content: '$defines $includes $asmflags', description: 'ASM $out' }); n.rule('link', { - command: `${ninjaWrap} $ld /nologo /OUT:$out @$out.rsp`, + command: `${nw} $ld /nologo /OUT:$out @$out.rsp`, rspfile: '$out.rsp', rspfile_content: '$in_newline $libs $ldflags', pool: 'link_pool', @@ -55,7 +52,7 @@ win.ninjaRules = function ninjaRules(n, outDir, generatorFlags, params) { }); n.rule('alink', { - command: `${ninjaWrap} $ar /nologo /ignore:4221 /OUT:$out @$out.rsp`, + command: `${nw} $ar /nologo /ignore:4221 /OUT:$out @$out.rsp`, rspfile: '$out.rsp', rspfile_content: '$in_newline $libs $arflags', pool: 'link_pool', @@ -63,8 +60,7 @@ win.ninjaRules = function ninjaRules(n, outDir, generatorFlags, params) { }); n.rule('solink', { - command: `${ninjaWrap} $ld /IMPLIB:$out.lib /nologo /DLL /OUT:$out ` + - '@$out.rsp', + command: `${nw} $ld /IMPLIB:$out.lib /nologo /DLL /OUT:$out @$out.rsp`, rspfile: '$out.rsp', rspfile_content: '$in_newline $libs $ldflags', pool: 'link_pool', @@ -364,112 +360,13 @@ win.detectVersion = function detectVersion() { throw new Error('No known Visual Studio version found, sorry!'); }; -const IMPORTANT_VARS = - /^(include|lib|libpath|path|pathext|systemroot|temp|tmp)=(.*)$/i; - -function formatEnvBlock(lines) { - let res = ''; - lines.forEach((line) => { - const match = line.match(IMPORTANT_VARS); - if (match === null) - return; - - res += match[1].toUpperCase() + '=' + match[2] + '\0'; - }); - return res; -} - -win.getMSVSVersion = function getMSVSVersion(version) { - const env = process.env; - - if (!version) - version = env['GYP_MSVS_VERSION'] || 'auto'; - - // Try to find a MSVS installation - if (version === 'auto' && env['VS140COMNTOOLS'] || version === '2015') - return '2015'; - if (version === 'auto' && env['VS120COMNTOOLS'] || version === '2013') - return '2013'; - if (version === 'auto' && env['VS100COMNTOOLS'] || version === '2010') - return '2010'; - - return 'auto'; -}; - -win.getOSBits = function getOSBits() { - const env = process.env; - - // PROCESSOR_ARCHITEW6432 - is a system arch - // PROCESSOR_ARCHITECTURE - is a session arch - const hostArch = env['PROCESSOR_ARCHITEW6432'] || - env['PROCESSOR_ARCHITECTURE']; - if (hostArch === 'AMD64') - return 64; - else - return 32; -}; - -win.genEnvironment = function genEnvironment(outDir, version, arch) { - const env = process.env; - let tools; - - // Try to find a MSVS installation - if (version === 'auto' && env['VS140COMNTOOLS'] || version === '2015') { - version = '2015'; - tools = path.join(env.VS140COMNTOOLS, '..', '..'); - } - if (version === 'auto' && env['VS120COMNTOOLS'] || version === '2013') { - version = '2013'; - tools = path.join(env.VS120COMNTOOLS, '..', '..'); - } - // TODO(indutny): more versions? - if (version === 'auto' && env['VS100COMNTOOLS'] || version === '2010') { - version = '2010'; - tools = path.join(env.VS120COMNTOOLS, '..', '..'); - } - // TODO(indutny): does it work with MSVS Express? - - if (version === 'auto') { - gyp.bindings.error('No Visual Studio found. When building - please ' + - 'run `ninja` from the MSVS console'); - return; - } - - // NOTE: Largely inspired by MSVSVersion.py - const bits = win.getOSBits(); - - let vcvars; - // TODO(indutny): proper escape for the .bat file - if (arch === 'ia32') { - if (bits === 64) - vcvars = '"' + path.join(tools, 'VC', 'vcvarsall.bat') + '" amd64_x86'; - else - vcvars = '"' + path.join(tools, 'Common7', 'Tools', 'vsvars32.bat') + '"'; - } else if (arch === 'x64') { - let arg; - if (bits === 64) - arg = 'amd64'; - else - arg = 'x86_amd64'; - vcvars = '"' + path.join(tools, 'VC', 'vcvarsall.bat') + '" ' + arg; - } else { - throw new Error(`Arch: '${arch}' is not supported on windows`); - } - - let lines; - try { - lines = gyp.bindings.execSync(`${vcvars} & set`, { env: {} }).toString() - .split(/\r\n/g); - } catch (e) { - gyp.bindings.error(e.message); - return; - } - - const envBlock = formatEnvBlock(lines); - const envFile = 'environment.' + arch; - - fs.writeFileSync(path.join(outDir, envFile), - envBlock); +win.genEnvironment = function genEnvironment(outDir, target_arch) { + const env = gyp.bindings.win.resolveDevEnvironment(target_arch); + const envBlock = Object.keys(env) + .map(key => `${key}=${env[key]}`) + .join('\0'); + const envFile = win.getEnvFileName(target_arch); + fs.writeFileSync(path.join(outDir, envFile), envBlock); return envFile; }; diff --git a/package.json b/package.json index 44915fc..f66c40a 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "rimraf": "^2.5.2" }, "optionalDependencies": { + "windows-autoconf": "^1.3.0", "ninja.js": "^1.1.0" }, "dependencies": {