Skip to content

Commit

Permalink
win: detect VS2017 & refactor envfile generation
Browse files Browse the repository at this point in the history
and harden ninja's `cmd` wrap mechanism
  • Loading branch information
refack committed Feb 24, 2017
1 parent 0c6f178 commit d44e5b9
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 139 deletions.
3 changes: 2 additions & 1 deletion lib/gyp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions lib/gyp/bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ exports.process = {
env: process.env,
cwd: () => process.cwd(),
platform: process.platform,
arch: process.arch,
exit: (code) => process.exit(code)
};

Expand All @@ -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
};
}
});
29 changes: 17 additions & 12 deletions lib/gyp/generator/ninja/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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, {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down
149 changes: 23 additions & 126 deletions lib/gyp/platform/win.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -32,39 +30,37 @@ 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',
description: 'LINK $out'
});

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',
description: 'ALINK $out'
});

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',
Expand Down Expand Up @@ -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;
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"rimraf": "^2.5.2"
},
"optionalDependencies": {
"windows-autoconf": "^1.3.0",
"ninja.js": "^1.1.0"
},
"dependencies": {
Expand Down

0 comments on commit d44e5b9

Please sign in to comment.