feat: convert all internal functions to async/await

BREAKING CHANGE: All internal functions have been coverted to return
promises and no longer accept callbacks. This is not a breaking change
for users but may be breaking to consumers of `node-gyp` if you are
requiring internal functions directly.
This commit is contained in:
Luke Karrys 2023-10-27 15:52:13 -07:00
parent 1b3bd341b4
commit 355622f4aa
20 changed files with 768 additions and 1062 deletions

View file

@ -68,7 +68,7 @@ if (dir) {
} }
} }
function run () { async function run () {
const command = prog.todo.shift() const command = prog.todo.shift()
if (!command) { if (!command) {
// done! // done!
@ -77,30 +77,28 @@ function run () {
return return
} }
prog.commands[command.name](command.args, function (err) { try {
if (err) { const args = await prog.commands[command.name](command.args) ?? []
log.error(command.name + ' error')
log.error('stack', err.stack)
errorMessage()
log.error('not ok')
return process.exit(1)
}
if (command.name === 'list') { if (command.name === 'list') {
const versions = arguments[1] if (args.length) {
if (versions.length > 0) { args.forEach((version) => console.log(version))
versions.forEach(function (version) {
console.log(version)
})
} else { } else {
console.log('No node development files installed. Use `node-gyp install` to install a version.') console.log('No node development files installed. Use `node-gyp install` to install a version.')
} }
} else if (arguments.length >= 2) { } else if (args.length >= 1) {
console.log.apply(console, [].slice.call(arguments, 1)) console.log(...args.slice(1))
} }
// now run the next command in the queue // now run the next command in the queue
process.nextTick(run) return run()
}) } catch (err) {
log.error(command.name + ' error')
log.error('stack', err.stack)
errorMessage()
log.error('not ok')
return process.exit(1)
}
} }
process.on('exit', function (code) { process.on('exit', function (code) {

View file

@ -202,13 +202,7 @@ async function build (gyp, argv) {
await new Promise((resolve, reject) => proc.on('exit', async (code, signal) => { await new Promise((resolve, reject) => proc.on('exit', async (code, signal) => {
if (buildBinsDir) { if (buildBinsDir) {
// Clean up the build-time dependency symlinks: // Clean up the build-time dependency symlinks:
if (fs.rm) { await fs.rm(buildBinsDir, { recursive: true })
// fs.rm is only available in Node 14+
await fs.rm(buildBinsDir, { recursive: true })
} else {
// Only used for old node, as recursive rmdir is deprecated in Node 14+
await fs.rmdir(buildBinsDir, { recursive: true })
}
} }
if (code !== 0) { if (code !== 0) {
@ -222,7 +216,5 @@ async function build (gyp, argv) {
} }
} }
module.exports = function (gyp, argv, callback) { module.exports = build
build(gyp, argv).then(callback.bind(undefined, null), callback)
}
module.exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module' module.exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module'

View file

@ -1,6 +1,6 @@
'use strict' 'use strict'
const fs = require('fs/promises') const fs = require('graceful-fs').promises
const log = require('./log') const log = require('./log')
async function clean (gyp, argv) { async function clean (gyp, argv) {
@ -11,7 +11,5 @@ async function clean (gyp, argv) {
await fs.rm(buildDir, { recursive: true, force: true }) await fs.rm(buildDir, { recursive: true, force: true })
} }
module.exports = function (gyp, argv, callback) { module.exports = clean
clean(gyp, argv).then(callback.bind(undefined, null), callback)
}
module.exports.usage = 'Removes any generated build files and the "out" dir' module.exports.usage = 'Removes any generated build files and the "out" dir'

View file

@ -1,6 +1,6 @@
'use strict' 'use strict'
const fs = require('graceful-fs') const { openSync, closeSync, promises: fs } = require('graceful-fs')
const path = require('path') const path = require('path')
const log = require('./log') const log = require('./log')
const os = require('os') const os = require('os')
@ -8,40 +8,28 @@ const processRelease = require('./process-release')
const win = process.platform === 'win32' const win = process.platform === 'win32'
const findNodeDirectory = require('./find-node-directory') const findNodeDirectory = require('./find-node-directory')
const createConfigGypi = require('./create-config-gypi') const createConfigGypi = require('./create-config-gypi')
const msgFormat = require('util').format const { format: msgFormat } = require('util')
const findPython = require('./find-python') const findPython = require('./find-python')
let findVisualStudio const findVisualStudio = win ? require('./find-visualstudio') : null
if (win) {
findVisualStudio = require('./find-visualstudio')
}
function configure (gyp, argv, callback) { async function configure (gyp, argv) {
let python
const buildDir = path.resolve('build') const buildDir = path.resolve('build')
const configNames = ['config.gypi', 'common.gypi'] const configNames = ['config.gypi', 'common.gypi']
const configs = [] const configs = []
let nodeDir let nodeDir
const release = processRelease(argv, gyp, process.version, process.release) const release = processRelease(argv, gyp, process.version, process.release)
findPython(gyp.opts.python, function (err, found) { const python = await findPython(gyp.opts.python)
if (err) { return getNodeDir()
callback(err)
} else {
python = found
getNodeDir()
}
})
function getNodeDir () { async function getNodeDir () {
// 'python' should be set by now // 'python' should be set by now
process.env.PYTHON = python process.env.PYTHON = python
if (gyp.opts.nodedir) { if (gyp.opts.nodedir) {
// --nodedir was specified. use that for the dev files // --nodedir was specified. use that for the dev files
nodeDir = gyp.opts.nodedir.replace(/^~/, os.homedir()) nodeDir = gyp.opts.nodedir.replace(/^~/, os.homedir())
log.verbose('get node dir', 'compiling against specified --nodedir dev files: %s', nodeDir) log.verbose('get node dir', 'compiling against specified --nodedir dev files: %s', nodeDir)
createBuildDir()
} else { } else {
// if no --nodedir specified, ensure node dependencies are installed // if no --nodedir specified, ensure node dependencies are installed
if ('v' + release.version !== process.version) { if ('v' + release.version !== process.version) {
@ -54,87 +42,66 @@ function configure (gyp, argv, callback) {
if (!release.semver) { if (!release.semver) {
// could not parse the version string with semver // could not parse the version string with semver
return callback(new Error('Invalid version number: ' + release.version)) throw new Error('Invalid version number: ' + release.version)
} }
// If the tarball option is set, always remove and reinstall the headers // If the tarball option is set, always remove and reinstall the headers
// into devdir. Otherwise only install if they're not already there. // into devdir. Otherwise only install if they're not already there.
gyp.opts.ensure = !gyp.opts.tarball gyp.opts.ensure = !gyp.opts.tarball
gyp.commands.install([release.version], function (err) { await gyp.commands.install([release.version])
if (err) {
return callback(err) log.verbose('get node dir', 'target node version installed:', release.versionDir)
} nodeDir = path.resolve(gyp.devDir, release.versionDir)
log.verbose('get node dir', 'target node version installed:', release.versionDir)
nodeDir = path.resolve(gyp.devDir, release.versionDir)
createBuildDir()
})
} }
return createBuildDir()
} }
function createBuildDir () { async function createBuildDir () {
log.verbose('build dir', 'attempting to create "build" dir: %s', buildDir) log.verbose('build dir', 'attempting to create "build" dir: %s', buildDir)
fs.mkdir(buildDir, { recursive: true }, function (err, isNew) { const isNew = await fs.mkdir(buildDir, { recursive: true })
if (err) { log.verbose(
return callback(err) 'build dir', '"build" dir needed to be created?', isNew ? 'Yes' : 'No'
} )
log.verbose( const vsInfo = win ? await findVisualStudio(release.semver, gyp.opts['msvs-version']) : null
'build dir', '"build" dir needed to be created?', isNew ? 'Yes' : 'No' return createConfigFile(vsInfo)
)
if (win) {
findVisualStudio(release.semver, gyp.opts['msvs-version'],
createConfigFile)
} else {
createConfigFile()
}
})
} }
function createConfigFile (err, vsInfo) { async function createConfigFile (vsInfo) {
if (err) { if (win) {
return callback(err)
}
if (process.platform === 'win32') {
process.env.GYP_MSVS_VERSION = Math.min(vsInfo.versionYear, 2015) process.env.GYP_MSVS_VERSION = Math.min(vsInfo.versionYear, 2015)
process.env.GYP_MSVS_OVERRIDE_PATH = vsInfo.path process.env.GYP_MSVS_OVERRIDE_PATH = vsInfo.path
} }
createConfigGypi({ gyp, buildDir, nodeDir, vsInfo, python }).then(configPath => { const configPath = await createConfigGypi({ gyp, buildDir, nodeDir, vsInfo, python })
configs.push(configPath) configs.push(configPath)
findConfigs() return findConfigs()
}).catch(err => {
callback(err)
})
} }
function findConfigs () { async function findConfigs () {
const name = configNames.shift() const name = configNames.shift()
if (!name) { if (!name) {
return runGyp() return runGyp()
} }
const fullPath = path.resolve(name) const fullPath = path.resolve(name)
log.verbose(name, 'checking for gypi file: %s', fullPath) log.verbose(name, 'checking for gypi file: %s', fullPath)
fs.stat(fullPath, function (err) { try {
if (err) { await fs.stat(fullPath)
if (err.code === 'ENOENT') { log.verbose(name, 'found gypi file')
findConfigs() // check next gypi filename configs.push(fullPath)
} else { } catch (err) {
callback(err) // ENOENT will check next gypi filename
} if (err.code !== 'ENOENT') {
} else { throw err
log.verbose(name, 'found gypi file')
configs.push(fullPath)
findConfigs()
} }
})
}
function runGyp (err) {
if (err) {
return callback(err)
} }
return findConfigs()
}
async function runGyp () {
if (!~argv.indexOf('-f') && !~argv.indexOf('--format')) { if (!~argv.indexOf('-f') && !~argv.indexOf('--format')) {
if (win) { if (win) {
log.verbose('gyp', 'gyp format was not specified; forcing "msvs"') log.verbose('gyp', 'gyp format was not specified; forcing "msvs"')
@ -189,7 +156,7 @@ function configure (gyp, argv, callback) {
} else { } else {
const msg = msgFormat('Could not find node.%s file in %s', ext, nodeRootDir) const msg = msgFormat('Could not find node.%s file in %s', ext, nodeRootDir)
log.error(logprefix, 'Could not find exports file') log.error(logprefix, 'Could not find exports file')
return callback(new Error(msg)) throw new Error(msg)
} }
} }
@ -227,7 +194,7 @@ function configure (gyp, argv, callback) {
} else if (release.version.split('.')[0] >= 16) { } else if (release.version.split('.')[0] >= 16) {
// zoslib is only shipped in Node v16 and above. // zoslib is only shipped in Node v16 and above.
log.error(logprefix, msg) log.error(logprefix, msg)
return callback(new Error(msg)) throw new Error(msg)
} }
} }
@ -235,78 +202,78 @@ function configure (gyp, argv, callback) {
const gypScript = path.resolve(__dirname, '..', 'gyp', 'gyp_main.py') const gypScript = path.resolve(__dirname, '..', 'gyp', 'gyp_main.py')
const addonGypi = path.resolve(__dirname, '..', 'addon.gypi') const addonGypi = path.resolve(__dirname, '..', 'addon.gypi')
let commonGypi = path.resolve(nodeDir, 'include/node/common.gypi') let commonGypi = path.resolve(nodeDir, 'include/node/common.gypi')
fs.stat(commonGypi, function (err) { try {
if (err) { await fs.stat(commonGypi)
commonGypi = path.resolve(nodeDir, 'common.gypi') } catch (err) {
} commonGypi = path.resolve(nodeDir, 'common.gypi')
let outputDir = 'build'
if (win) {
// Windows expects an absolute path
outputDir = buildDir
}
const nodeGypDir = path.resolve(__dirname, '..')
let nodeLibFile = path.join(nodeDir,
!gyp.opts.nodedir ? '<(target_arch)' : '$(Configuration)',
release.name + '.lib')
argv.push('-I', addonGypi)
argv.push('-I', commonGypi)
argv.push('-Dlibrary=shared_library')
argv.push('-Dvisibility=default')
argv.push('-Dnode_root_dir=' + nodeDir)
if (process.platform === 'aix' || process.platform === 'os390' || process.platform === 'os400') {
argv.push('-Dnode_exp_file=' + nodeExpFile)
if (process.platform === 'os390' && zoslibIncDir) {
argv.push('-Dzoslib_include_dir=' + zoslibIncDir)
}
}
argv.push('-Dnode_gyp_dir=' + nodeGypDir)
// Do this to keep Cygwin environments happy, else the unescaped '\' gets eaten up,
// resulting in bad paths, Ex c:parentFolderfolderanotherFolder instead of c:\parentFolder\folder\anotherFolder
if (win) {
nodeLibFile = nodeLibFile.replace(/\\/g, '\\\\')
}
argv.push('-Dnode_lib_file=' + nodeLibFile)
argv.push('-Dmodule_root_dir=' + process.cwd())
argv.push('-Dnode_engine=' +
(gyp.opts.node_engine || process.jsEngine || 'v8'))
argv.push('--depth=.')
argv.push('--no-parallel')
// tell gyp to write the Makefile/Solution files into output_dir
argv.push('--generator-output', outputDir)
// tell make to write its output into the same dir
argv.push('-Goutput_dir=.')
// enforce use of the "binding.gyp" file
argv.unshift('binding.gyp')
// execute `gyp` from the current target nodedir
argv.unshift(gypScript)
// make sure python uses files that came with this particular node package
const pypath = [path.join(__dirname, '..', 'gyp', 'pylib')]
if (process.env.PYTHONPATH) {
pypath.push(process.env.PYTHONPATH)
}
process.env.PYTHONPATH = pypath.join(win ? ';' : ':')
const cp = gyp.spawn(python, argv)
cp.on('exit', onCpExit)
})
}
function onCpExit (code) {
if (code !== 0) {
callback(new Error('`gyp` failed with exit code: ' + code))
} else {
// we're done
callback()
} }
let outputDir = 'build'
if (win) {
// Windows expects an absolute path
outputDir = buildDir
}
const nodeGypDir = path.resolve(__dirname, '..')
let nodeLibFile = path.join(nodeDir,
!gyp.opts.nodedir ? '<(target_arch)' : '$(Configuration)',
release.name + '.lib')
argv.push('-I', addonGypi)
argv.push('-I', commonGypi)
argv.push('-Dlibrary=shared_library')
argv.push('-Dvisibility=default')
argv.push('-Dnode_root_dir=' + nodeDir)
if (process.platform === 'aix' || process.platform === 'os390' || process.platform === 'os400') {
argv.push('-Dnode_exp_file=' + nodeExpFile)
if (process.platform === 'os390' && zoslibIncDir) {
argv.push('-Dzoslib_include_dir=' + zoslibIncDir)
}
}
argv.push('-Dnode_gyp_dir=' + nodeGypDir)
// Do this to keep Cygwin environments happy, else the unescaped '\' gets eaten up,
// resulting in bad paths, Ex c:parentFolderfolderanotherFolder instead of c:\parentFolder\folder\anotherFolder
if (win) {
nodeLibFile = nodeLibFile.replace(/\\/g, '\\\\')
}
argv.push('-Dnode_lib_file=' + nodeLibFile)
argv.push('-Dmodule_root_dir=' + process.cwd())
argv.push('-Dnode_engine=' +
(gyp.opts.node_engine || process.jsEngine || 'v8'))
argv.push('--depth=.')
argv.push('--no-parallel')
// tell gyp to write the Makefile/Solution files into output_dir
argv.push('--generator-output', outputDir)
// tell make to write its output into the same dir
argv.push('-Goutput_dir=.')
// enforce use of the "binding.gyp" file
argv.unshift('binding.gyp')
// execute `gyp` from the current target nodedir
argv.unshift(gypScript)
// make sure python uses files that came with this particular node package
const pypath = [path.join(__dirname, '..', 'gyp', 'pylib')]
if (process.env.PYTHONPATH) {
pypath.push(process.env.PYTHONPATH)
}
process.env.PYTHONPATH = pypath.join(win ? ';' : ':')
await new Promise((resolve, reject) => {
const cp = gyp.spawn(python, argv)
cp.on('exit', (code) => {
if (code !== 0) {
reject(new Error('`gyp` failed with exit code: ' + code))
} else {
// we're done
resolve()
}
})
})
} }
} }
@ -320,13 +287,13 @@ function findAccessibleSync (logprefix, dir, candidates) {
const candidate = path.resolve(dir, candidates[next]) const candidate = path.resolve(dir, candidates[next])
let fd let fd
try { try {
fd = fs.openSync(candidate, 'r') fd = openSync(candidate, 'r')
} catch (e) { } catch (e) {
// this candidate was not found or not readable, do nothing // this candidate was not found or not readable, do nothing
log.silly(logprefix, 'Could not open %s: %s', candidate, e.message) log.silly(logprefix, 'Could not open %s: %s', candidate, e.message)
continue continue
} }
fs.closeSync(fd) closeSync(fd)
log.silly(logprefix, 'Found readable %s', candidate) log.silly(logprefix, 'Found readable %s', candidate)
return candidate return candidate
} }

View file

@ -1,6 +1,6 @@
'use strict' 'use strict'
const fs = require('graceful-fs') const fs = require('graceful-fs').promises
const log = require('./log') const log = require('./log')
const path = require('path') const path = require('path')
@ -24,7 +24,7 @@ async function getBaseConfigGypi ({ gyp, nodeDir }) {
if (shouldReadConfigGypi && nodeDir) { if (shouldReadConfigGypi && nodeDir) {
try { try {
const baseConfigGypiPath = path.resolve(nodeDir, 'include/node/config.gypi') const baseConfigGypiPath = path.resolve(nodeDir, 'include/node/config.gypi')
const baseConfigGypi = await fs.promises.readFile(baseConfigGypiPath) const baseConfigGypi = await fs.readFile(baseConfigGypiPath)
return parseConfigGypi(baseConfigGypi.toString()) return parseConfigGypi(baseConfigGypi.toString())
} catch (err) { } catch (err) {
log.warn('read config.gypi', err.message) log.warn('read config.gypi', err.message)
@ -138,7 +138,7 @@ async function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo, python }) {
const json = JSON.stringify(config, boolsToString, 2) const json = JSON.stringify(config, boolsToString, 2)
log.verbose('build/' + configFilename, 'writing out config file: %s', configPath) log.verbose('build/' + configFilename, 'writing out config file: %s', configPath)
await fs.promises.writeFile(configPath, [prefix, json, ''].join('\n')) await fs.writeFile(configPath, [prefix, json, ''].join('\n'))
return configPath return configPath
} }

View file

@ -2,8 +2,8 @@
const log = require('./log') const log = require('./log')
const semver = require('semver') const semver = require('semver')
const cp = require('child_process') const { _extend: extend } = require('util') // eslint-disable-line n/no-deprecated-api
const extend = require('util')._extend // eslint-disable-line const { execFile } = require('./util')
const win = process.platform === 'win32' const win = process.platform === 'win32'
const systemDrive = process.env.SystemDrive || 'C:' const systemDrive = process.env.SystemDrive || 'C:'
@ -38,8 +38,7 @@ function getOsUserInfo () {
} catch (e) {} } catch (e) {}
} }
function PythonFinder (configPython, callback) { function PythonFinder (configPython) {
this.callback = callback
this.configPython = configPython this.configPython = configPython
this.errorLog = [] this.errorLog = []
} }
@ -51,7 +50,7 @@ PythonFinder.prototype = {
semverRange: '>=3.6.0', semverRange: '>=3.6.0',
// These can be overridden for testing: // These can be overridden for testing:
execFile: cp.execFile, execFile,
env: process.env, env: process.env,
win, win,
pyLauncher: 'py.exe', pyLauncher: 'py.exe',
@ -66,11 +65,10 @@ PythonFinder.prototype = {
// Find Python by trying a sequence of possibilities. // Find Python by trying a sequence of possibilities.
// Ignore errors, keep trying until Python is found. // Ignore errors, keep trying until Python is found.
findPython: function findPython () { findPython: async function findPython () {
const SKIP = 0; const FAIL = 1 const SKIP = 0
const toCheck = getChecks.apply(this) const FAIL = 1
const toCheck = (() => {
function getChecks () {
if (this.env.NODE_GYP_FORCE_PYTHON) { if (this.env.NODE_GYP_FORCE_PYTHON) {
return [{ return [{
before: () => { before: () => {
@ -79,8 +77,7 @@ PythonFinder.prototype = {
this.addLog('- process.env.NODE_GYP_FORCE_PYTHON is ' + this.addLog('- process.env.NODE_GYP_FORCE_PYTHON is ' +
`"${this.env.NODE_GYP_FORCE_PYTHON}"`) `"${this.env.NODE_GYP_FORCE_PYTHON}"`)
}, },
check: this.checkCommand, check: () => this.checkCommand(this.env.NODE_GYP_FORCE_PYTHON)
arg: this.env.NODE_GYP_FORCE_PYTHON
}] }]
} }
@ -97,8 +94,7 @@ PythonFinder.prototype = {
this.addLog('- "--python=" or "npm config get python" is ' + this.addLog('- "--python=" or "npm config get python" is ' +
`"${this.configPython}"`) `"${this.configPython}"`)
}, },
check: this.checkCommand, check: () => this.checkCommand(this.configPython)
arg: this.configPython
}, },
{ {
before: () => { before: () => {
@ -111,8 +107,7 @@ PythonFinder.prototype = {
'variable PYTHON') 'variable PYTHON')
this.addLog(`- process.env.PYTHON is "${this.env.PYTHON}"`) this.addLog(`- process.env.PYTHON is "${this.env.PYTHON}"`)
}, },
check: this.checkCommand, check: () => this.checkCommand(this.env.PYTHON)
arg: this.env.PYTHON
} }
] ]
@ -122,20 +117,18 @@ PythonFinder.prototype = {
this.addLog( this.addLog(
'checking if the py launcher can be used to find Python 3') 'checking if the py launcher can be used to find Python 3')
}, },
check: this.checkPyLauncher check: () => this.checkPyLauncher()
}) })
} }
checks.push(...[ checks.push(...[
{ {
before: () => { this.addLog('checking if "python3" can be used') }, before: () => { this.addLog('checking if "python3" can be used') },
check: this.checkCommand, check: () => this.checkCommand('python3')
arg: 'python3'
}, },
{ {
before: () => { this.addLog('checking if "python" can be used') }, before: () => { this.addLog('checking if "python" can be used') },
check: this.checkCommand, check: () => this.checkCommand('python')
arg: 'python'
} }
]) ])
@ -143,49 +136,37 @@ PythonFinder.prototype = {
for (let i = 0; i < this.winDefaultLocations.length; ++i) { for (let i = 0; i < this.winDefaultLocations.length; ++i) {
const location = this.winDefaultLocations[i] const location = this.winDefaultLocations[i]
checks.push({ checks.push({
before: () => { before: () => this.addLog(`checking if Python is ${location}`),
this.addLog('checking if Python is ' + check: () => this.checkExecPath(location)
`${location}`)
},
check: this.checkExecPath,
arg: location
}) })
} }
} }
return checks return checks
} })()
function runChecks (err) { for (const check of toCheck) {
this.log.silly('runChecks: err = %j', (err && err.stack) || err) const before = check.before()
const check = toCheck.shift()
if (!check) {
return this.fail()
}
const before = check.before.apply(this)
if (before === SKIP) { if (before === SKIP) {
return runChecks.apply(this) continue
} }
if (before === FAIL) { if (before === FAIL) {
return this.fail() return this.fail()
} }
try {
const args = [runChecks.bind(this)] return await check.check()
if (check.arg) { } catch (err) {
args.unshift(check.arg) this.log.silly('runChecks: err = %j', (err && err.stack) || err)
} }
check.check.apply(this, args)
} }
runChecks.apply(this) return this.fail()
}, },
// Check if command is a valid Python to use. // Check if command is a valid Python to use.
// Will exit the Python finder on success. // Will exit the Python finder on success.
// If on Windows, run in a CMD shell to support BAT/CMD launchers. // If on Windows, run in a CMD shell to support BAT/CMD launchers.
checkCommand: function checkCommand (command, errorCallback) { checkCommand: async function checkCommand (command) {
let exec = command let exec = command
let args = this.argsExecutable let args = this.argsExecutable
let shell = false let shell = false
@ -197,18 +178,18 @@ PythonFinder.prototype = {
} }
this.log.verbose(`- executing "${command}" to get executable path`) this.log.verbose(`- executing "${command}" to get executable path`)
this.run(exec, args, shell, function (err, execPath) { // Possible outcomes:
// Possible outcomes: // - Error: not in PATH, not executable or execution fails
// - Error: not in PATH, not executable or execution fails // - Gibberish: the next command to check version will fail
// - Gibberish: the next command to check version will fail // - Absolute path to executable
// - Absolute path to executable try {
if (err) { const execPath = await this.run(exec, args, shell)
this.addLog(`- "${command}" is not in PATH or produced an error`)
return errorCallback(err)
}
this.addLog(`- executable path is "${execPath}"`) this.addLog(`- executable path is "${execPath}"`)
this.checkExecPath(execPath, errorCallback) return this.checkExecPath(execPath)
}.bind(this)) } catch (err) {
this.addLog(`- "${command}" is not in PATH or produced an error`)
throw err
}
}, },
// Check if the py launcher can find a valid Python to use. // Check if the py launcher can find a valid Python to use.
@ -221,37 +202,31 @@ PythonFinder.prototype = {
// the first command line argument. Since "py.exe -3" would be an invalid // the first command line argument. Since "py.exe -3" would be an invalid
// executable for "execFile", we have to use the launcher to figure out // executable for "execFile", we have to use the launcher to figure out
// where the actual "python.exe" executable is located. // where the actual "python.exe" executable is located.
checkPyLauncher: function checkPyLauncher (errorCallback) { checkPyLauncher: async function checkPyLauncher () {
this.log.verbose( this.log.verbose(`- executing "${this.pyLauncher}" to get Python 3 executable path`)
`- executing "${this.pyLauncher}" to get Python 3 executable path`) // Possible outcomes: same as checkCommand
this.run(this.pyLauncher, ['-3', ...this.argsExecutable], false, try {
function (err, execPath) { const execPath = await this.run(this.pyLauncher, ['-3', ...this.argsExecutable], false)
// Possible outcomes: same as checkCommand this.addLog(`- executable path is "${execPath}"`)
if (err) { return this.checkExecPath(execPath)
this.addLog( } catch (err) {
`- "${this.pyLauncher}" is not in PATH or produced an error`) this.addLog(`- "${this.pyLauncher}" is not in PATH or produced an error`)
return errorCallback(err) throw err
} }
this.addLog(`- executable path is "${execPath}"`)
this.checkExecPath(execPath, errorCallback)
}.bind(this))
}, },
// Check if a Python executable is the correct version to use. // Check if a Python executable is the correct version to use.
// Will exit the Python finder on success. // Will exit the Python finder on success.
checkExecPath: function checkExecPath (execPath, errorCallback) { checkExecPath: async function checkExecPath (execPath) {
this.log.verbose(`- executing "${execPath}" to get version`) this.log.verbose(`- executing "${execPath}" to get version`)
this.run(execPath, this.argsVersion, false, function (err, version) { // Possible outcomes:
// Possible outcomes: // - Error: executable can not be run (likely meaning the command wasn't
// - Error: executable can not be run (likely meaning the command wasn't // a Python executable and the previous command produced gibberish)
// a Python executable and the previous command produced gibberish) // - Gibberish: somehow the last command produced an executable path,
// - Gibberish: somehow the last command produced an executable path, // this will fail when verifying the version
// this will fail when verifying the version // - Version of the Python executable
// - Version of the Python executable try {
if (err) { const version = await this.run(execPath, this.argsVersion, false)
this.addLog(`- "${execPath}" could not be run`)
return errorCallback(err)
}
this.addLog(`- version is "${version}"`) this.addLog(`- version is "${version}"`)
const range = new semver.Range(this.semverRange) const range = new semver.Range(this.semverRange)
@ -262,21 +237,22 @@ PythonFinder.prototype = {
this.log.silly('range.test() threw:\n%s', err.stack) this.log.silly('range.test() threw:\n%s', err.stack)
this.addLog(`- "${execPath}" does not have a valid version`) this.addLog(`- "${execPath}" does not have a valid version`)
this.addLog('- is it a Python executable?') this.addLog('- is it a Python executable?')
return errorCallback(err) throw err
} }
if (!valid) { if (!valid) {
this.addLog(`- version is ${version} - should be ${this.semverRange}`) this.addLog(`- version is ${version} - should be ${this.semverRange}`)
this.addLog('- THIS VERSION OF PYTHON IS NOT SUPPORTED') this.addLog('- THIS VERSION OF PYTHON IS NOT SUPPORTED')
return errorCallback(new Error( throw new Error(`Found unsupported Python version ${version}`)
`Found unsupported Python version ${version}`))
} }
this.succeed(execPath, version) return this.succeed(execPath, version)
}.bind(this)) } catch (err) {
this.addLog(`- "${execPath}" could not be run`)
throw err
}
}, },
// Run an executable or shell command, trimming the output. // Run an executable or shell command, trimming the output.
run: function run (exec, args, shell, callback) { run: async function run (exec, args, shell) {
const env = extend({}, this.env) const env = extend({}, this.env)
env.TERM = 'dumb' env.TERM = 'dumb'
const opts = { env, shell } const opts = { env, shell }
@ -285,27 +261,20 @@ PythonFinder.prototype = {
this.log.silly('execFile: args = %j', args) this.log.silly('execFile: args = %j', args)
this.log.silly('execFile: opts = %j', opts) this.log.silly('execFile: opts = %j', opts)
try { try {
this.execFile(exec, args, opts, execFileCallback.bind(this)) const [err, stdout, stderr] = await this.execFile(exec, args, opts)
} catch (err) {
this.log.silly('execFile: threw:\n%s', err.stack)
return callback(err)
}
function execFileCallback (err, stdout, stderr) {
this.log.silly('execFile result: err = %j', (err && err.stack) || err) this.log.silly('execFile result: err = %j', (err && err.stack) || err)
this.log.silly('execFile result: stdout = %j', stdout) this.log.silly('execFile result: stdout = %j', stdout)
this.log.silly('execFile result: stderr = %j', stderr) this.log.silly('execFile result: stderr = %j', stderr)
if (err) { return stdout.trim()
return callback(err) } catch (err) {
} this.log.silly('execFile: threw:\n%s', err.stack)
const execPath = stdout.trim() throw err
callback(null, execPath)
} }
}, },
succeed: function succeed (execPath, version) { succeed: function succeed (execPath, version) {
this.log.info(`using Python version ${version} found at "${execPath}"`) this.log.info(`using Python version ${version} found at "${execPath}"`)
process.nextTick(this.callback.bind(null, null, execPath)) return execPath
}, },
fail: function fail () { fail: function fail () {
@ -333,15 +302,11 @@ PythonFinder.prototype = {
].join('\n') ].join('\n')
this.log.error(`\n${errorLog}\n\n${info}\n`) this.log.error(`\n${errorLog}\n\n${info}\n`)
process.nextTick(this.callback.bind(null, new Error( throw new Error('Could not find any Python installation to use')
'Could not find any Python installation to use')))
} }
} }
function findPython (configPython, callback) { const findPython = async (configPython) => new PythonFinder(configPython).findPython()
const finder = new PythonFinder(configPython, callback)
finder.findPython()
}
module.exports = findPython module.exports = findPython
module.exports.test = { module.exports.test = {

View file

@ -1,21 +1,13 @@
'use strict' 'use strict'
const log = require('./log') const log = require('./log')
const execFile = require('child_process').execFile const { existsSync } = require('fs')
const fs = require('fs') const { win32: path } = require('path')
const path = require('path').win32 const { regSearchKeys, execFile } = require('./util')
const regSearchKeys = require('./util').regSearchKeys
function findVisualStudio (nodeSemver, configMsvsVersion, callback) { function VisualStudioFinder (nodeSemver, configMsvsVersion) {
const finder = new VisualStudioFinder(nodeSemver, configMsvsVersion,
callback)
finder.findVisualStudio()
}
function VisualStudioFinder (nodeSemver, configMsvsVersion, callback) {
this.nodeSemver = nodeSemver this.nodeSemver = nodeSemver
this.configMsvsVersion = configMsvsVersion this.configMsvsVersion = configMsvsVersion
this.callback = callback
this.errorLog = [] this.errorLog = []
this.validVersions = [] this.validVersions = []
} }
@ -32,7 +24,7 @@ VisualStudioFinder.prototype = {
this.errorLog.push(message) this.errorLog.push(message)
}, },
findVisualStudio: function findVisualStudio () { findVisualStudio: async function findVisualStudio () {
this.configVersionYear = null this.configVersionYear = null
this.configPath = null this.configPath = null
if (this.configMsvsVersion) { if (this.configMsvsVersion) {
@ -59,29 +51,27 @@ VisualStudioFinder.prototype = {
this.addLog('VCINSTALLDIR not set, not running in VS Command Prompt') this.addLog('VCINSTALLDIR not set, not running in VS Command Prompt')
} }
this.findVisualStudio2017OrNewer((info) => { const checks = [
() => this.findVisualStudio2017OrNewer(),
() => this.findVisualStudio2015(),
() => this.findVisualStudio2013()
]
for (const check of checks) {
const info = await check()
if (info) { if (info) {
return this.succeed(info) return this.succeed(info)
} }
this.findVisualStudio2015((info) => { }
if (info) {
return this.succeed(info) return this.fail()
}
this.findVisualStudio2013((info) => {
if (info) {
return this.succeed(info)
}
this.fail()
})
})
})
}, },
succeed: function succeed (info) { succeed: function succeed (info) {
this.log.info(`using VS${info.versionYear} (${info.version}) found at:` + this.log.info(`using VS${info.versionYear} (${info.version}) found at:` +
`\n"${info.path}"` + `\n"${info.path}"` +
'\nrun with --verbose for detailed information') '\nrun with --verbose for detailed information')
process.nextTick(this.callback.bind(null, null, info)) return info
}, },
fail: function fail () { fail: function fail () {
@ -118,13 +108,12 @@ VisualStudioFinder.prototype = {
].join('\n') ].join('\n')
this.log.error(`\n${errorLog}\n\n${infoLog}\n`) this.log.error(`\n${errorLog}\n\n${infoLog}\n`)
process.nextTick(this.callback.bind(null, new Error( throw new Error('Could not find any Visual Studio installation to use')
'Could not find any Visual Studio installation to use')))
}, },
// Invoke the PowerShell script to get information about Visual Studio 2017 // Invoke the PowerShell script to get information about Visual Studio 2017
// or newer installations // or newer installations
findVisualStudio2017OrNewer: function findVisualStudio2017OrNewer (cb) { findVisualStudio2017OrNewer: async function findVisualStudio2017OrNewer () {
const ps = path.join(process.env.SystemRoot, 'System32', const ps = path.join(process.env.SystemRoot, 'System32',
'WindowsPowerShell', 'v1.0', 'powershell.exe') 'WindowsPowerShell', 'v1.0', 'powershell.exe')
const csFile = path.join(__dirname, 'Find-VisualStudio.cs') const csFile = path.join(__dirname, 'Find-VisualStudio.cs')
@ -137,22 +126,19 @@ VisualStudioFinder.prototype = {
] ]
this.log.silly('Running', ps, psArgs) this.log.silly('Running', ps, psArgs)
const child = execFile(ps, psArgs, { encoding: 'utf8' }, const [err, stdout, stderr] = await execFile(ps, psArgs, { encoding: 'utf8' })
(err, stdout, stderr) => { return this.parseData(err, stdout, stderr)
this.parseData(err, stdout, stderr, cb)
})
child.stdin.end()
}, },
// Parse the output of the PowerShell script and look for an installation // Parse the output of the PowerShell script and look for an installation
// of Visual Studio 2017 or newer to use // of Visual Studio 2017 or newer to use
parseData: function parseData (err, stdout, stderr, cb) { parseData: function parseData (err, stdout, stderr) {
this.log.silly('PS stderr = %j', stderr) this.log.silly('PS stderr = %j', stderr)
const failPowershell = () => { const failPowershell = () => {
this.addLog( this.addLog(
'could not use PowerShell to find Visual Studio 2017 or newer, try re-running with \'--loglevel silly\' for more details') 'could not use PowerShell to find Visual Studio 2017 or newer, try re-running with \'--loglevel silly\' for more details')
cb(null) return null
} }
if (err) { if (err) {
@ -228,12 +214,12 @@ VisualStudioFinder.prototype = {
continue continue
} }
return cb(info) return info
} }
this.addLog( this.addLog(
'could not find a version of Visual Studio 2017 or newer to use') 'could not find a version of Visual Studio 2017 or newer to use')
cb(null) return null
}, },
// Helper - process version information // Helper - process version information
@ -266,7 +252,7 @@ VisualStudioFinder.prototype = {
}, },
msBuildPathExists: function msBuildPathExists (path) { msBuildPathExists: function msBuildPathExists (path) {
return fs.existsSync(path) return existsSync(path)
}, },
// Helper - process MSBuild information // Helper - process MSBuild information
@ -356,11 +342,11 @@ VisualStudioFinder.prototype = {
}, },
// Find an installation of Visual Studio 2015 to use // Find an installation of Visual Studio 2015 to use
findVisualStudio2015: function findVisualStudio2015 (cb) { findVisualStudio2015: async function findVisualStudio2015 () {
if (this.nodeSemver.major >= 19) { if (this.nodeSemver.major >= 19) {
this.addLog( this.addLog(
'not looking for VS2015 as it is only supported up to Node.js 18') 'not looking for VS2015 as it is only supported up to Node.js 18')
return cb(null) return null
} }
return this.findOldVS({ return this.findOldVS({
version: '14.0', version: '14.0',
@ -368,15 +354,15 @@ VisualStudioFinder.prototype = {
versionMinor: 0, versionMinor: 0,
versionYear: 2015, versionYear: 2015,
toolset: 'v140' toolset: 'v140'
}, cb) })
}, },
// Find an installation of Visual Studio 2013 to use // Find an installation of Visual Studio 2013 to use
findVisualStudio2013: function findVisualStudio2013 (cb) { findVisualStudio2013: async function findVisualStudio2013 () {
if (this.nodeSemver.major >= 9) { if (this.nodeSemver.major >= 9) {
this.addLog( this.addLog(
'not looking for VS2013 as it is only supported up to Node.js 8') 'not looking for VS2013 as it is only supported up to Node.js 8')
return cb(null) return null
} }
return this.findOldVS({ return this.findOldVS({
version: '12.0', version: '12.0',
@ -384,47 +370,44 @@ VisualStudioFinder.prototype = {
versionMinor: 0, versionMinor: 0,
versionYear: 2013, versionYear: 2013,
toolset: 'v120' toolset: 'v120'
}, cb) })
}, },
// Helper - common code for VS2013 and VS2015 // Helper - common code for VS2013 and VS2015
findOldVS: function findOldVS (info, cb) { findOldVS: async function findOldVS (info) {
const regVC7 = ['HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7', const regVC7 = ['HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7',
'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7'] 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7']
const regMSBuild = 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions' const regMSBuild = 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions'
this.addLog(`looking for Visual Studio ${info.versionYear}`) this.addLog(`looking for Visual Studio ${info.versionYear}`)
this.regSearchKeys(regVC7, info.version, [], (err, res) => { try {
if (err) { let res = await this.regSearchKeys(regVC7, info.version, [])
this.addLog('- not found')
return cb(null)
}
const vsPath = path.resolve(res, '..') const vsPath = path.resolve(res, '..')
this.addLog(`- found in "${vsPath}"`) this.addLog(`- found in "${vsPath}"`)
const msBuildRegOpts = process.arch === 'ia32' ? [] : ['/reg:32'] const msBuildRegOpts = process.arch === 'ia32' ? [] : ['/reg:32']
this.regSearchKeys([`${regMSBuild}\\${info.version}`],
'MSBuildToolsPath', msBuildRegOpts, (err, res) => {
if (err) {
this.addLog(
'- could not find MSBuild in registry for this version')
return cb(null)
}
const msBuild = path.join(res, 'MSBuild.exe') try {
this.addLog(`- MSBuild in "${msBuild}"`) res = await this.regSearchKeys([`${regMSBuild}\\${info.version}`], 'MSBuildToolsPath', msBuildRegOpts)
} catch (err) {
this.addLog('- could not find MSBuild in registry for this version')
return null
}
if (!this.checkConfigVersion(info.versionYear, vsPath)) { const msBuild = path.join(res, 'MSBuild.exe')
return cb(null) this.addLog(`- MSBuild in "${msBuild}"`)
}
info.path = vsPath if (!this.checkConfigVersion(info.versionYear, vsPath)) {
info.msBuild = msBuild return null
info.sdk = null }
cb(info)
}) info.path = vsPath
}) info.msBuild = msBuild
info.sdk = null
return info
} catch (err) {
this.addLog('- not found')
return null
}
}, },
// After finding a usable version of Visual Studio: // After finding a usable version of Visual Studio:
@ -455,6 +438,8 @@ VisualStudioFinder.prototype = {
} }
} }
const findVisualStudio = async (nodeSemver, configMsvsVersion) => new VisualStudioFinder(nodeSemver, configMsvsVersion).findVisualStudio()
module.exports = findVisualStudio module.exports = findVisualStudio
module.exports.test = { module.exports.test = {
VisualStudioFinder, VisualStudioFinder,

View file

@ -1,26 +1,20 @@
'use strict' 'use strict'
const fs = require('graceful-fs') const { createWriteStream, promises: fs } = require('graceful-fs')
const os = require('os') const os = require('os')
const { backOff } = require('exponential-backoff') const { backOff } = require('exponential-backoff')
const rm = require('rimraf')
const tar = require('tar') const tar = require('tar')
const path = require('path') const path = require('path')
const util = require('util') const { Transform, promises: { pipeline } } = require('stream')
const stream = require('stream')
const crypto = require('crypto') const crypto = require('crypto')
const log = require('./log') const log = require('./log')
const semver = require('semver') const semver = require('semver')
const fetch = require('make-fetch-happen') const fetch = require('make-fetch-happen')
const processRelease = require('./process-release') const processRelease = require('./process-release')
const win = process.platform === 'win32' const win = process.platform === 'win32'
const streamPipeline = util.promisify(stream.pipeline)
/** async function install (gyp, argv) {
* @param {typeof import('graceful-fs')} fs console.log()
*/
async function install (fs, gyp, argv) {
const release = processRelease(argv, gyp, process.version, process.release) const release = processRelease(argv, gyp, process.version, process.release)
// Detecting target_arch based on logic from create-cnfig-gyp.js. Used on Windows only. // Detecting target_arch based on logic from create-cnfig-gyp.js. Used on Windows only.
const arch = win ? (gyp.opts.target_arch || gyp.opts.arch || process.arch || 'ia32') : '' const arch = win ? (gyp.opts.target_arch || gyp.opts.arch || process.arch || 'ia32') : ''
@ -60,7 +54,7 @@ async function install (fs, gyp, argv) {
if (gyp.opts.ensure) { if (gyp.opts.ensure) {
log.verbose('install', '--ensure was passed, so won\'t reinstall if already installed') log.verbose('install', '--ensure was passed, so won\'t reinstall if already installed')
try { try {
await fs.promises.stat(devDir) await fs.stat(devDir)
} catch (err) { } catch (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
log.verbose('install', 'version not already installed, continuing with install', release.version) log.verbose('install', 'version not already installed, continuing with install', release.version)
@ -78,7 +72,7 @@ async function install (fs, gyp, argv) {
const installVersionFile = path.resolve(devDir, 'installVersion') const installVersionFile = path.resolve(devDir, 'installVersion')
let installVersion = 0 let installVersion = 0
try { try {
const ver = await fs.promises.readFile(installVersionFile, 'ascii') const ver = await fs.readFile(installVersionFile, 'ascii')
installVersion = parseInt(ver, 10) || 0 installVersion = parseInt(ver, 10) || 0
} catch (err) { } catch (err) {
if (err.code !== 'ENOENT') { if (err.code !== 'ENOENT') {
@ -100,7 +94,7 @@ async function install (fs, gyp, argv) {
log.verbose('on Windows; need to check node.lib') log.verbose('on Windows; need to check node.lib')
const nodeLibPath = path.resolve(devDir, arch, 'node.lib') const nodeLibPath = path.resolve(devDir, arch, 'node.lib')
try { try {
await fs.promises.stat(nodeLibPath) await fs.stat(nodeLibPath)
} catch (err) { } catch (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
log.verbose('install', `version not already installed for ${arch}, continuing with install`, release.version) log.verbose('install', `version not already installed for ${arch}, continuing with install`, release.version)
@ -126,12 +120,12 @@ async function install (fs, gyp, argv) {
async function copyDirectory (src, dest) { async function copyDirectory (src, dest) {
try { try {
await fs.promises.stat(src) await fs.stat(src)
} catch { } catch {
throw new Error(`Missing source directory for copy: ${src}`) throw new Error(`Missing source directory for copy: ${src}`)
} }
await fs.promises.mkdir(dest, { recursive: true }) await fs.mkdir(dest, { recursive: true })
const entries = await fs.promises.readdir(src, { withFileTypes: true }) const entries = await fs.readdir(src, { withFileTypes: true })
for (const entry of entries) { for (const entry of entries) {
if (entry.isDirectory()) { if (entry.isDirectory()) {
await copyDirectory(path.join(src, entry.name), path.join(dest, entry.name)) await copyDirectory(path.join(src, entry.name), path.join(dest, entry.name))
@ -140,12 +134,12 @@ async function install (fs, gyp, argv) {
// Windows so use an exponential backoff to resolve collisions // Windows so use an exponential backoff to resolve collisions
await backOff(async () => { await backOff(async () => {
try { try {
await fs.promises.copyFile(path.join(src, entry.name), path.join(dest, entry.name)) await fs.copyFile(path.join(src, entry.name), path.join(dest, entry.name))
} catch (err) { } catch (err) {
// if ensure, check if file already exists and that's good enough // if ensure, check if file already exists and that's good enough
if (gyp.opts.ensure && err.code === 'EBUSY') { if (gyp.opts.ensure && err.code === 'EBUSY') {
try { try {
await fs.promises.stat(path.join(dest, entry.name)) await fs.stat(path.join(dest, entry.name))
return return
} catch {} } catch {}
} }
@ -163,7 +157,7 @@ async function install (fs, gyp, argv) {
// first create the dir for the node dev files // first create the dir for the node dev files
try { try {
const created = await fs.promises.mkdir(devDir, { recursive: true }) const created = await fs.mkdir(devDir, { recursive: true })
if (created) { if (created) {
log.verbose('created devDir', created) log.verbose('created devDir', created)
@ -208,7 +202,7 @@ async function install (fs, gyp, argv) {
// on Windows there can be file errors from tar if parallel installs // on Windows there can be file errors from tar if parallel installs
// are happening (not uncommon with multiple native modules) so // are happening (not uncommon with multiple native modules) so
// extract the tarball to a temp directory first and then copy over // extract the tarball to a temp directory first and then copy over
const tarExtractDir = win ? await fs.promises.mkdtemp(path.join(os.tmpdir(), 'node-gyp-tmp-')) : devDir const tarExtractDir = win ? await fs.mkdtemp(path.join(os.tmpdir(), 'node-gyp-tmp-')) : devDir
try { try {
if (shouldDownloadTarball) { if (shouldDownloadTarball) {
@ -228,7 +222,7 @@ async function install (fs, gyp, argv) {
throw new Error(`${res.status} response downloading ${release.tarballUrl}`) throw new Error(`${res.status} response downloading ${release.tarballUrl}`)
} }
await streamPipeline( await pipeline(
res.body, res.body,
// content checksum // content checksum
new ShaSum((_, checksum) => { new ShaSum((_, checksum) => {
@ -267,7 +261,7 @@ async function install (fs, gyp, argv) {
// need to download node.lib // need to download node.lib
...(win ? [downloadNodeLib()] : []), ...(win ? [downloadNodeLib()] : []),
// write the "installVersion" file // write the "installVersion" file
fs.promises.writeFile(installVersionPath, gyp.package.installVersion + '\n'), fs.writeFile(installVersionPath, gyp.package.installVersion + '\n'),
// Only download SHASUMS.txt if we downloaded something in need of SHA verification // Only download SHASUMS.txt if we downloaded something in need of SHA verification
...(!tarPath || win ? [downloadShasums()] : []) ...(!tarPath || win ? [downloadShasums()] : [])
]) ])
@ -289,7 +283,7 @@ async function install (fs, gyp, argv) {
if (tarExtractDir !== devDir) { if (tarExtractDir !== devDir) {
try { try {
// try to cleanup temp dir // try to cleanup temp dir
await util.promisify(rm)(tarExtractDir) await fs.rm(tarExtractDir, { recursive: true })
} catch { } catch {
log.warn('failed to clean up temp tarball extract directory') log.warn('failed to clean up temp tarball extract directory')
} }
@ -329,7 +323,7 @@ async function install (fs, gyp, argv) {
log.verbose(name, 'dir', dir) log.verbose(name, 'dir', dir)
log.verbose(name, 'url', libUrl) log.verbose(name, 'url', libUrl)
await fs.promises.mkdir(dir, { recursive: true }) await fs.mkdir(dir, { recursive: true })
log.verbose('streaming', name, 'to:', targetLibPath) log.verbose('streaming', name, 'to:', targetLibPath)
const res = await download(gyp, libUrl) const res = await download(gyp, libUrl)
@ -339,13 +333,13 @@ async function install (fs, gyp, argv) {
throw new Error(`${res.status} status code downloading ${name}`) throw new Error(`${res.status} status code downloading ${name}`)
} }
return streamPipeline( return pipeline(
res.body, res.body,
new ShaSum((_, checksum) => { new ShaSum((_, checksum) => {
contentShasums[libPath] = checksum contentShasums[libPath] = checksum
log.verbose('content checksum', libPath, checksum) log.verbose('content checksum', libPath, checksum)
}), }),
fs.createWriteStream(targetLibPath) createWriteStream(targetLibPath)
) )
} // downloadNodeLib() } // downloadNodeLib()
} // go() } // go()
@ -363,7 +357,7 @@ async function install (fs, gyp, argv) {
async function rollback (err) { async function rollback (err) {
log.warn('install', 'got an error, rolling back install') log.warn('install', 'got an error, rolling back install')
// roll-back the install if anything went wrong // roll-back the install if anything went wrong
await util.promisify(gyp.commands.remove)([release.versionDir]) await gyp.commands.remove([release.versionDir])
throw err throw err
} }
@ -394,11 +388,11 @@ async function install (fs, gyp, argv) {
log.verbose('tmpdir == cwd', 'automatically will remove dev files after to save disk space') log.verbose('tmpdir == cwd', 'automatically will remove dev files after to save disk space')
gyp.todo.push({ name: 'remove', args: argv }) gyp.todo.push({ name: 'remove', args: argv })
} }
return util.promisify(gyp.commands.install)([noretry].concat(argv)) return gyp.commands.install([noretry].concat(argv))
} }
} }
class ShaSum extends stream.Transform { class ShaSum extends Transform {
constructor (callback) { constructor (callback) {
super() super()
this._callback = callback this._callback = callback
@ -442,14 +436,12 @@ async function download (gyp, url) {
async function readCAFile (filename) { async function readCAFile (filename) {
// The CA file can contain multiple certificates so split on certificate // The CA file can contain multiple certificates so split on certificate
// boundaries. [\S\s]*? is used to match everything including newlines. // boundaries. [\S\s]*? is used to match everything including newlines.
const ca = await fs.promises.readFile(filename, 'utf8') const ca = await fs.readFile(filename, 'utf8')
const re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g const re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g
return ca.match(re) return ca.match(re)
} }
module.exports = function (gyp, argv, callback) { module.exports = install
install(fs, gyp, argv).then(callback.bind(undefined, null), callback)
}
module.exports.test = { module.exports.test = {
download, download,
install, install,

View file

@ -1,26 +1,25 @@
'use strict' 'use strict'
const fs = require('graceful-fs') const fs = require('graceful-fs').promises
const log = require('./log') const log = require('./log')
function list (gyp, args, callback) { async function list (gyp, args) {
const devDir = gyp.devDir const devDir = gyp.devDir
log.verbose('list', 'using node-gyp dir:', devDir) log.verbose('list', 'using node-gyp dir:', devDir)
fs.readdir(devDir, onreaddir) let versions = []
try {
function onreaddir (err, versions) { const dir = await fs.readdir(devDir)
if (Array.isArray(dir)) {
versions = dir.filter((v) => v !== 'current')
}
} catch (err) {
if (err && err.code !== 'ENOENT') { if (err && err.code !== 'ENOENT') {
return callback(err) throw err
} }
if (Array.isArray(versions)) {
versions = versions.filter(function (v) { return v !== 'current' })
} else {
versions = []
}
callback(null, versions)
} }
return versions
} }
module.exports = list module.exports = list

View file

@ -5,7 +5,7 @@ const nopt = require('nopt')
const log = require('./log') const log = require('./log')
const childProcess = require('child_process') const childProcess = require('child_process')
const EE = require('events').EventEmitter const EE = require('events').EventEmitter
const inherits = require('util').inherits const { inherits } = require('util')
const commands = [ const commands = [
// Module build commands // Module build commands
'build', 'build',
@ -27,17 +27,12 @@ function gyp () {
} }
function Gyp () { function Gyp () {
const self = this
this.devDir = '' this.devDir = ''
this.commands = {}
commands.forEach(function (command) { this.commands = commands.reduce((acc, command) => {
self.commands[command] = function (argv, callback) { acc[command] = (argv) => require('./' + command)(this, argv)
log.verbose('command', command, argv) return acc
return require('./' + command)(self, argv, callback) }, {})
}
})
} }
inherits(Gyp, EE) inherits(Gyp, EE)
exports.Gyp = Gyp exports.Gyp = Gyp

View file

@ -1,12 +1,11 @@
'use strict' 'use strict'
function rebuild (gyp, argv, callback) { async function rebuild (gyp, argv) {
gyp.todo.push( gyp.todo.push(
{ name: 'clean', args: [] } { name: 'clean', args: [] }
, { name: 'configure', args: argv } , { name: 'configure', args: argv }
, { name: 'build', args: [] } , { name: 'build', args: [] }
) )
process.nextTick(callback)
} }
module.exports = rebuild module.exports = rebuild

View file

@ -1,6 +1,6 @@
'use strict' 'use strict'
const fs = require('fs/promises') const fs = require('graceful-fs').promises
const path = require('path') const path = require('path')
const log = require('./log') const log = require('./log')
const semver = require('semver') const semver = require('semver')
@ -39,7 +39,5 @@ async function remove (gyp, argv) {
await fs.rm(versionPath, { recursive: true, force: true }) await fs.rm(versionPath, { recursive: true, force: true })
} }
module.exports = function (gyp, argv, callback) { module.exports = remove
remove(gyp, argv).then(callback.bind(undefined, null), callback)
}
module.exports.usage = 'Removes the node development files for the specified version' module.exports.usage = 'Removes the node development files for the specified version'

View file

@ -1,50 +1,55 @@
'use strict' 'use strict'
const log = require('./log') const log = require('./log')
const execFile = require('child_process').execFile const cp = require('child_process')
const path = require('path') const path = require('path')
function regGetValue (key, value, addOpts, cb) { const execFile = async (...args) => new Promise((resolve) => {
const child = cp.execFile(...args, (...a) => resolve(a))
child.stdin.end()
})
async function regGetValue (key, value, addOpts) {
const outReValue = value.replace(/\W/g, '.') const outReValue = value.replace(/\W/g, '.')
const outRe = new RegExp(`^\\s+${outReValue}\\s+REG_\\w+\\s+(\\S.*)$`, 'im') const outRe = new RegExp(`^\\s+${outReValue}\\s+REG_\\w+\\s+(\\S.*)$`, 'im')
const reg = path.join(process.env.SystemRoot, 'System32', 'reg.exe') const reg = path.join(process.env.SystemRoot, 'System32', 'reg.exe')
const regArgs = ['query', key, '/v', value].concat(addOpts) const regArgs = ['query', key, '/v', value].concat(addOpts)
log.silly('reg', 'running', reg, regArgs) log.silly('reg', 'running', reg, regArgs)
const child = execFile(reg, regArgs, { encoding: 'utf8' }, const [err, stdout, stderr] = await execFile(reg, regArgs, { encoding: 'utf8' })
function (err, stdout, stderr) {
log.silly('reg', 'reg.exe stdout = %j', stdout)
if (err || stderr.trim() !== '') {
log.silly('reg', 'reg.exe err = %j', err && (err.stack || err))
log.silly('reg', 'reg.exe stderr = %j', stderr)
return cb(err, stderr)
}
const result = outRe.exec(stdout) log.silly('reg', 'reg.exe stdout = %j', stdout)
if (!result) { if (err || stderr.trim() !== '') {
log.silly('reg', 'error parsing stdout') log.silly('reg', 'reg.exe err = %j', err && (err.stack || err))
return cb(new Error('Could not parse output of reg.exe')) log.silly('reg', 'reg.exe stderr = %j', stderr)
} if (err) {
log.silly('reg', 'found: %j', result[1]) throw err
cb(null, result[1]) }
}) throw new Error(stderr)
child.stdin.end() }
const result = outRe.exec(stdout)
if (!result) {
log.silly('reg', 'error parsing stdout')
throw new Error('Could not parse output of reg.exe')
}
log.silly('reg', 'found: %j', result[1])
return result[1]
} }
function regSearchKeys (keys, value, addOpts, cb) { async function regSearchKeys (keys, value, addOpts) {
let i = 0 for (const key of keys) {
const search = () => { try {
log.silly('reg-search', 'looking for %j in %j', value, keys[i]) return await regGetValue(key, value, addOpts)
regGetValue(keys[i], value, addOpts, (err, res) => { } catch {
++i continue
if (err && i < keys.length) { return search() } }
cb(err, res)
})
} }
search()
} }
module.exports = { module.exports = {
execFile,
regGetValue, regGetValue,
regSearchKeys regSearchKeys
} }

View file

@ -1,140 +0,0 @@
'use strict'
const fs = require('graceful-fs')
const childProcess = require('child_process')
function startsWith (str, search, pos) {
if (String.prototype.startsWith) {
return str.startsWith(search, pos)
}
return str.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search
}
function processExecSync (file, args, options) {
let error, command
command = makeCommand(file, args)
/*
this function emulates child_process.execSync for legacy node <= 0.10.x
derived from https://github.com/gvarsanyi/sync-exec/blob/master/js/sync-exec.js
*/
options = options || {}
// init timeout
const timeout = Date.now() + options.timeout
// init tmpdir
let osTempBase = '/tmp'
const os = determineOS()
osTempBase = '/tmp'
if (process.env.TMP) {
osTempBase = process.env.TMP
}
if (osTempBase[osTempBase.length - 1] !== '/') {
osTempBase += '/'
}
const tmpdir = osTempBase + 'processExecSync.' + Date.now() + Math.random()
fs.mkdirSync(tmpdir)
// init command
if (os === 'linux') {
command = '(' + command + ' > ' + tmpdir + '/stdout 2> ' + tmpdir +
'/stderr); echo $? > ' + tmpdir + '/status'
} else {
command = '(' + command + ' > ' + tmpdir + '/stdout 2> ' + tmpdir +
'/stderr) | echo %errorlevel% > ' + tmpdir + '/status | exit'
}
// init child
const child = childProcess.exec(command, options)
const maxTry = 100000 // increases the test time by 6 seconds on win-2016-node-0.10
let tryCount = 0
while (tryCount < maxTry) {
try {
const x = fs.readFileSync(tmpdir + '/status')
if (x.toString() === '0') {
break
}
} catch (ignore) {}
tryCount++
if (Date.now() > timeout) {
error = child
break
}
}
['stdout', 'stderr', 'status'].forEach(function (file) {
child[file] = fs.readFileSync(tmpdir + '/' + file, options.encoding)
setTimeout(unlinkFile, 500, tmpdir + '/' + file)
})
child.status = Number(child.status)
if (child.status !== 0) {
error = child
}
try {
fs.rmdirSync(tmpdir)
} catch (ignore) {}
if (error) {
throw error
}
return child.stdout
}
function makeCommand (file, args) {
let command, quote
command = file
if (args.length > 0) {
for (const i in args) {
command = command + ' '
if (args[i][0] === '-') {
command = command + args[i]
} else {
if (!quote) {
command = command + '"'
quote = true
}
command = command + args[i]
if (quote) {
if (args.length === (parseInt(i) + 1)) {
command = command + '"'
}
}
}
}
}
return command
}
function determineOS () {
let os = ''
let tmpVar = ''
if (process.env.OSTYPE) {
tmpVar = process.env.OSTYPE
} else if (process.env.OS) {
tmpVar = process.env.OS
} else {
// default is linux
tmpVar = 'linux'
}
if (startsWith(tmpVar, 'linux')) {
os = 'linux'
}
if (startsWith(tmpVar, 'win')) {
os = 'win'
}
return os
}
function unlinkFile (file) {
fs.unlinkSync(file)
}
module.exports = processExecSync

View file

@ -4,12 +4,10 @@ const { describe, it } = require('mocha')
const assert = require('assert') const assert = require('assert')
const path = require('path') const path = require('path')
const fs = require('graceful-fs') const fs = require('graceful-fs')
const childProcess = require('child_process') const { execFileSync, execFile } = require('child_process')
const os = require('os') const os = require('os')
const addonPath = path.resolve(__dirname, 'node_modules', 'hello_world') const addonPath = path.resolve(__dirname, 'node_modules', 'hello_world')
const nodeGyp = path.resolve(__dirname, '..', 'bin', 'node-gyp.js') const nodeGyp = path.resolve(__dirname, '..', 'bin', 'node-gyp.js')
const execFileSync = childProcess.execFileSync || require('./process-exec-sync')
const execFile = childProcess.execFile
function runHello (hostProcess) { function runHello (hostProcess) {
if (!hostProcess) { if (!hostProcess) {

View file

@ -9,13 +9,12 @@ const log = require('../lib/log')
const requireInject = require('require-inject') const requireInject = require('require-inject')
const configure = requireInject('../lib/configure', { const configure = requireInject('../lib/configure', {
'graceful-fs': { 'graceful-fs': {
openSync: function () { return 0 }, openSync: () => 0,
closeSync: function () { }, closeSync: () => {},
writeFile: function (file, data, cb) { cb() },
stat: function (file, cb) { cb(null, {}) },
mkdir: function (dir, options, cb) { cb() },
promises: { promises: {
writeFile: function (file, data) { return Promise.resolve(null) } stat: async () => ({}),
mkdir: async () => {},
writeFile: async () => {}
} }
} }
}) })

View file

@ -4,7 +4,6 @@ const { describe, it, after } = require('mocha')
const assert = require('assert') const assert = require('assert')
const fs = require('fs/promises') const fs = require('fs/promises')
const path = require('path') const path = require('path')
const util = require('util')
const http = require('http') const http = require('http')
const https = require('https') const https = require('https')
const install = require('../lib/install') const install = require('../lib/install')
@ -176,7 +175,7 @@ describe('download', function () {
prog.parseArgv([]) prog.parseArgv([])
prog.devDir = devDir prog.devDir = devDir
log.level = 'warn' log.level = 'warn'
await util.promisify(install)(prog, []) await install(prog, [])
const data = await fs.readFile(path.join(expectedDir, 'installVersion'), 'utf8') const data = await fs.readFile(path.join(expectedDir, 'installVersion'), 'utf8')
assert.strictEqual(data, '11\n', 'correct installVersion') assert.strictEqual(data, '11\n', 'correct installVersion')

View file

@ -4,22 +4,16 @@ delete process.env.PYTHON
const { describe, it } = require('mocha') const { describe, it } = require('mocha')
const assert = require('assert') const assert = require('assert')
const findPython = require('../lib/find-python') const { test: { PythonFinder, findPython: testFindPython } } = require('../lib/find-python')
const execFile = require('child_process').execFile const { execFile } = require('../lib/util')
const PythonFinder = findPython.test.PythonFinder
describe('find-python', function () { describe('find-python', function () {
it('find python', function () { it('find python', async function () {
findPython.test.findPython(null, function (err, found) { const found = await testFindPython(null)
assert.strictEqual(err, null) const [err, stdout, stderr] = await execFile(found, ['-V'], { encoding: 'utf-8' })
const proc = execFile(found, ['-V'], function (err, stdout, stderr) { assert.strictEqual(err, null)
assert.strictEqual(err, null) assert.ok(/Python 3/.test(stdout))
assert.ok(/Python 3/.test(stdout)) assert.strictEqual(stderr, '')
assert.strictEqual(stderr, '')
})
proc.stdout.setEncoding('utf-8')
proc.stderr.setEncoding('utf-8')
})
}) })
function poison (object, property) { function poison (object, property) {
@ -36,109 +30,105 @@ describe('find-python', function () {
Object.defineProperty(object, property, descriptor) Object.defineProperty(object, property, descriptor)
} }
function TestPythonFinder () { function TestPythonFinder () { PythonFinder.apply(this, arguments) }
PythonFinder.apply(this, arguments)
}
TestPythonFinder.prototype = Object.create(PythonFinder.prototype) TestPythonFinder.prototype = Object.create(PythonFinder.prototype)
delete TestPythonFinder.prototype.env.NODE_GYP_FORCE_PYTHON delete TestPythonFinder.prototype.env.NODE_GYP_FORCE_PYTHON
const findPython = async (f) => {
try {
return { err: null, python: await f.findPython() }
} catch (err) {
return { err, python: null }
}
}
it('find python - python', function () { it('find python - python', async function () {
const f = new TestPythonFinder('python', done) const f = new TestPythonFinder('python')
f.execFile = function (program, args, opts, cb) { f.execFile = async function (program, args, opts) {
f.execFile = function (program, args, opts, cb) { f.execFile = async function (program, args, opts) {
poison(f, 'execFile') poison(f, 'execFile')
assert.strictEqual(program, '/path/python') assert.strictEqual(program, '/path/python')
assert.ok(/sys\.version_info/.test(args[1])) assert.ok(/sys\.version_info/.test(args[1]))
cb(null, '3.9.1') return [null, '3.9.1']
} }
assert.strictEqual(program, assert.strictEqual(program, process.platform === 'win32' ? '"python"' : 'python')
process.platform === 'win32' ? '"python"' : 'python')
assert.ok(/sys\.executable/.test(args[1])) assert.ok(/sys\.executable/.test(args[1]))
cb(null, '/path/python') return [null, '/path/python']
} }
f.findPython()
function done (err, python) { const { err, python } = await findPython(f)
assert.strictEqual(err, null) assert.strictEqual(err, null)
assert.strictEqual(python, '/path/python') assert.strictEqual(python, '/path/python')
}
}) })
it('find python - python too old', function () { it('find python - python too old', async function () {
const f = new TestPythonFinder(null, done) const f = new TestPythonFinder(null)
f.execFile = function (program, args, opts, cb) { f.execFile = async function (program, args, opts) {
if (/sys\.executable/.test(args[args.length - 1])) { if (/sys\.executable/.test(args[args.length - 1])) {
cb(null, '/path/python') return [null, '/path/python']
} else if (/sys\.version_info/.test(args[args.length - 1])) { } else if (/sys\.version_info/.test(args[args.length - 1])) {
cb(null, '2.3.4') return [null, '2.3.4']
} else { } else {
assert.fail() assert.fail()
} }
} }
f.findPython()
function done (err) { const { err } = await findPython(f)
assert.ok(/Could not find any Python/.test(err)) assert.ok(/Could not find any Python/.test(err))
assert.ok(/not supported/i.test(f.errorLog)) assert.ok(/not supported/i.test(f.errorLog))
}
}) })
it('find python - no python', function () { it('find python - no python', async function () {
const f = new TestPythonFinder(null, done) const f = new TestPythonFinder(null)
f.execFile = function (program, args, opts, cb) { f.execFile = async function (program, args, opts) {
if (/sys\.executable/.test(args[args.length - 1])) { if (/sys\.executable/.test(args[args.length - 1])) {
cb(new Error('not found')) throw new Error('not found')
} else if (/sys\.version_info/.test(args[args.length - 1])) { } else if (/sys\.version_info/.test(args[args.length - 1])) {
cb(new Error('not a Python executable')) throw new Error('not a Python executable')
} else { } else {
assert.fail() assert.fail()
} }
} }
f.findPython()
function done (err) { const { err } = await findPython(f)
assert.ok(/Could not find any Python/.test(err)) assert.ok(/Could not find any Python/.test(err))
assert.ok(/not in PATH/.test(f.errorLog)) assert.ok(/not in PATH/.test(f.errorLog))
}
}) })
it('find python - no python2, no python, unix', function () { it('find python - no python2, no python, unix', async function () {
const f = new TestPythonFinder(null, done) const f = new TestPythonFinder(null)
f.checkPyLauncher = assert.fail f.checkPyLauncher = assert.fail
f.win = false f.win = false
f.execFile = function (program, args, opts, cb) { f.execFile = async function (program, args, opts) {
if (/sys\.executable/.test(args[args.length - 1])) { if (/sys\.executable/.test(args[args.length - 1])) {
cb(new Error('not found')) throw new Error('not found')
} else { } else {
assert.fail() assert.fail()
} }
} }
f.findPython()
function done (err) { const { err } = await findPython(f)
assert.ok(/Could not find any Python/.test(err)) assert.ok(/Could not find any Python/.test(err))
assert.ok(/not in PATH/.test(f.errorLog)) assert.ok(/not in PATH/.test(f.errorLog))
}
}) })
it('find python - no python, use python launcher', function () { it('find python - no python, use python launcher', async function () {
const f = new TestPythonFinder(null, done) const f = new TestPythonFinder(null)
f.win = true f.win = true
f.execFile = function (program, args, opts, cb) { f.execFile = async function (program, args, opts) {
if (program === 'py.exe') { if (program === 'py.exe') {
assert.notStrictEqual(args.indexOf('-3'), -1) assert.notStrictEqual(args.indexOf('-3'), -1)
assert.notStrictEqual(args.indexOf('-c'), -1) assert.notStrictEqual(args.indexOf('-c'), -1)
return cb(null, 'Z:\\snake.exe') return [null, 'Z:\\snake.exe']
} }
if (/sys\.executable/.test(args[args.length - 1])) { if (/sys\.executable/.test(args[args.length - 1])) {
cb(new Error('not found')) throw new Error('not found')
} else if (f.winDefaultLocations.includes(program)) { } else if (f.winDefaultLocations.includes(program)) {
cb(new Error('not found')) throw new Error('not found')
} else if (/sys\.version_info/.test(args[args.length - 1])) { } else if (/sys\.version_info/.test(args[args.length - 1])) {
if (program === 'Z:\\snake.exe') { if (program === 'Z:\\snake.exe') {
cb(null, '3.9.0') return [null, '3.9.0']
} else { } else {
assert.fail() assert.fail()
} }
@ -146,58 +136,49 @@ describe('find-python', function () {
assert.fail() assert.fail()
} }
} }
f.findPython() const { err, python } = await findPython(f)
assert.strictEqual(err, null)
function done (err, python) { assert.strictEqual(python, 'Z:\\snake.exe')
assert.strictEqual(err, null)
assert.strictEqual(python, 'Z:\\snake.exe')
}
}) })
it('find python - no python, no python launcher, good guess', function () { it('find python - no python, no python launcher, good guess', async function () {
const f = new TestPythonFinder(null, done) const f = new TestPythonFinder(null)
f.win = true f.win = true
const expectedProgram = f.winDefaultLocations[0] const expectedProgram = f.winDefaultLocations[0]
f.execFile = function (program, args, opts, cb) { f.execFile = async function (program, args, opts) {
if (program === 'py.exe') { if (program === 'py.exe') {
return cb(new Error('not found')) throw new Error('not found')
} }
if (/sys\.executable/.test(args[args.length - 1])) { if (/sys\.executable/.test(args[args.length - 1])) {
cb(new Error('not found')) throw new Error('not found')
} else if (program === expectedProgram && } else if (program === expectedProgram &&
/sys\.version_info/.test(args[args.length - 1])) { /sys\.version_info/.test(args[args.length - 1])) {
cb(null, '3.7.3') return [null, '3.7.3']
} else { } else {
assert.fail() assert.fail()
} }
} }
f.findPython() const { err, python } = await findPython(f)
assert.strictEqual(err, null)
function done (err, python) { assert.ok(python === expectedProgram)
assert.strictEqual(err, null)
assert.ok(python === expectedProgram)
}
}) })
it('find python - no python, no python launcher, bad guess', function () { it('find python - no python, no python launcher, bad guess', async function () {
const f = new TestPythonFinder(null, done) const f = new TestPythonFinder(null)
f.win = true f.win = true
f.execFile = function (program, args, opts, cb) { f.execFile = async function (program, args, opts) {
if (/sys\.executable/.test(args[args.length - 1])) { if (/sys\.executable/.test(args[args.length - 1])) {
cb(new Error('not found')) throw new Error('not found')
} else if (/sys\.version_info/.test(args[args.length - 1])) { } else if (/sys\.version_info/.test(args[args.length - 1])) {
cb(new Error('not a Python executable')) throw new Error('not a Python executable')
} else { } else {
assert.fail() assert.fail()
} }
} }
f.findPython() const { err } = await findPython(f)
assert.ok(/Could not find any Python/.test(err))
function done (err) { assert.ok(/not in PATH/.test(f.errorLog))
assert.ok(/Could not find any Python/.test(err))
assert.ok(/not in PATH/.test(f.errorLog))
}
}) })
}) })

View file

@ -4,8 +4,7 @@ const { describe, it } = require('mocha')
const assert = require('assert') const assert = require('assert')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const findVisualStudio = require('../lib/find-visualstudio') const { test: { VisualStudioFinder } } = require('../lib/find-visualstudio')
const VisualStudioFinder = findVisualStudio.test.VisualStudioFinder
const semverV1 = { major: 1, minor: 0, patch: 0 } const semverV1 = { major: 1, minor: 0, patch: 0 }
@ -28,26 +27,22 @@ function poison (object, property) {
function TestVisualStudioFinder () { VisualStudioFinder.apply(this, arguments) } function TestVisualStudioFinder () { VisualStudioFinder.apply(this, arguments) }
TestVisualStudioFinder.prototype = Object.create(VisualStudioFinder.prototype) TestVisualStudioFinder.prototype = Object.create(VisualStudioFinder.prototype)
describe('find-visualstudio', function () { const findVisualStudio = async (finder) => {
it('VS2013', function () { try {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { return { err: null, info: await finder.findVisualStudio() }
assert.strictEqual(err, null) } catch (err) {
assert.deepStrictEqual(info, { return { err, info: null }
msBuild: 'C:\\MSBuild12\\MSBuild.exe', }
path: 'C:\\VS2013', }
sdk: null,
toolset: 'v120',
version: '12.0',
versionMajor: 12,
versionMinor: 0,
versionYear: 2013
})
})
finder.findVisualStudio2017OrNewer = (cb) => { describe('find-visualstudio', function () {
finder.parseData(new Error(), '', '', cb) it('VS2013', async function () {
const finder = new TestVisualStudioFinder(semverV1, null)
finder.findVisualStudio2017OrNewer = async () => {
return finder.parseData(new Error(), '', '')
} }
finder.regSearchKeys = (keys, value, addOpts, cb) => { finder.regSearchKeys = async (keys, value, addOpts) => {
for (let i = 0; i < keys.length; ++i) { for (let i = 0; i < keys.length; ++i) {
const fullName = `${keys[i]}\\${value}` const fullName = `${keys[i]}\\${value}`
switch (fullName) { switch (fullName) {
@ -56,35 +51,44 @@ describe('find-visualstudio', function () {
continue continue
case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0': case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0':
assert.ok(true, `expected search for registry value ${fullName}`) assert.ok(true, `expected search for registry value ${fullName}`)
return cb(null, 'C:\\VS2013\\VC\\') return 'C:\\VS2013\\VC\\'
case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\12.0\\MSBuildToolsPath': case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\12.0\\MSBuildToolsPath':
assert.ok(true, `expected search for registry value ${fullName}`) assert.ok(true, `expected search for registry value ${fullName}`)
return cb(null, 'C:\\MSBuild12\\') return 'C:\\MSBuild12\\'
default: default:
assert.fail(`unexpected search for registry value ${fullName}`) assert.fail(`unexpected search for registry value ${fullName}`)
} }
} }
return cb(new Error()) throw new Error()
} }
finder.findVisualStudio()
const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\MSBuild12\\MSBuild.exe',
path: 'C:\\VS2013',
sdk: null,
toolset: 'v120',
version: '12.0',
versionMajor: 12,
versionMinor: 0,
versionYear: 2013
})
}) })
it('VS2013 should not be found on new node versions', function () { it('VS2013 should not be found on new node versions', async function () {
const finder = new TestVisualStudioFinder({ const finder = new TestVisualStudioFinder({
major: 10, major: 10,
minor: 0, minor: 0,
patch: 0 patch: 0
}, null, (err, info) => { }, null)
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
})
finder.findVisualStudio2017OrNewer = (cb) => { finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures', 'VS_2017_Unusable.txt') const file = path.join(__dirname, 'fixtures', 'VS_2017_Unusable.txt')
const data = fs.readFileSync(file) const data = fs.readFileSync(file)
finder.parseData(null, data, '', cb) return finder.parseData(null, data, '')
} }
finder.regSearchKeys = (keys, value, addOpts, cb) => { finder.regSearchKeys = async (keys, value, addOpts) => {
for (let i = 0; i < keys.length; ++i) { for (let i = 0; i < keys.length; ++i) {
const fullName = `${keys[i]}\\${value}` const fullName = `${keys[i]}\\${value}`
switch (fullName) { switch (fullName) {
@ -95,49 +99,51 @@ describe('find-visualstudio', function () {
assert.fail(`unexpected search for registry value ${fullName}`) assert.fail(`unexpected search for registry value ${fullName}`)
} }
} }
return cb(new Error()) throw new Error()
} }
finder.findVisualStudio()
const { err, info } = await findVisualStudio(finder)
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
}) })
it('VS2015', function () { it('VS2015', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\MSBuild14\\MSBuild.exe',
path: 'C:\\VS2015',
sdk: null,
toolset: 'v140',
version: '14.0',
versionMajor: 14,
versionMinor: 0,
versionYear: 2015
})
})
finder.findVisualStudio2017OrNewer = (cb) => { finder.findVisualStudio2017OrNewer = async () => {
finder.parseData(new Error(), '', '', cb) return finder.parseData(new Error(), '', '')
} }
finder.regSearchKeys = (keys, value, addOpts, cb) => { finder.regSearchKeys = async (keys, value, addOpts) => {
for (let i = 0; i < keys.length; ++i) { for (let i = 0; i < keys.length; ++i) {
const fullName = `${keys[i]}\\${value}` const fullName = `${keys[i]}\\${value}`
switch (fullName) { switch (fullName) {
case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0': case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0':
assert.ok(true, `expected search for registry value ${fullName}`) assert.ok(true, `expected search for registry value ${fullName}`)
return cb(null, 'C:\\VS2015\\VC\\') return 'C:\\VS2015\\VC\\'
case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\14.0\\MSBuildToolsPath': case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\14.0\\MSBuildToolsPath':
assert.ok(true, `expected search for registry value ${fullName}`) assert.ok(true, `expected search for registry value ${fullName}`)
return cb(null, 'C:\\MSBuild14\\') return 'C:\\MSBuild14\\'
default: default:
assert.fail(`unexpected search for registry value ${fullName}`) assert.fail(`unexpected search for registry value ${fullName}`)
} }
} }
return cb(new Error()) throw new Error()
} }
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\MSBuild14\\MSBuild.exe',
path: 'C:\\VS2015',
sdk: null,
toolset: 'v140',
version: '14.0',
versionMajor: 14,
versionMinor: 0,
versionYear: 2015
})
}) })
it('error from PowerShell', function () { it('error from PowerShell', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, null) const finder = new TestVisualStudioFinder(semverV1, null, null)
finder.parseData(new Error(), '', '', (info) => { finder.parseData(new Error(), '', '', (info) => {
@ -146,7 +152,7 @@ describe('find-visualstudio', function () {
}) })
}) })
it('empty output from PowerShell', function () { it('empty output from PowerShell', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, null) const finder = new TestVisualStudioFinder(semverV1, null, null)
finder.parseData(null, '', '', (info) => { finder.parseData(null, '', '', (info) => {
@ -155,7 +161,7 @@ describe('find-visualstudio', function () {
}) })
}) })
it('output from PowerShell not JSON', function () { it('output from PowerShell not JSON', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, null) const finder = new TestVisualStudioFinder(semverV1, null, null)
finder.parseData(null, 'AAAABBBB', '', (info) => { finder.parseData(null, 'AAAABBBB', '', (info) => {
@ -164,7 +170,7 @@ describe('find-visualstudio', function () {
}) })
}) })
it('wrong JSON from PowerShell', function () { it('wrong JSON from PowerShell', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, null) const finder = new TestVisualStudioFinder(semverV1, null, null)
finder.parseData(null, '{}', '', (info) => { finder.parseData(null, '{}', '', (info) => {
@ -173,7 +179,7 @@ describe('find-visualstudio', function () {
}) })
}) })
it('empty JSON from PowerShell', function () { it('empty JSON from PowerShell', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, null) const finder = new TestVisualStudioFinder(semverV1, null, null)
finder.parseData(null, '[]', '', (info) => { finder.parseData(null, '[]', '', (info) => {
@ -182,7 +188,7 @@ describe('find-visualstudio', function () {
}) })
}) })
it('future version', function () { it('future version', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, null) const finder = new TestVisualStudioFinder(semverV1, null, null)
finder.parseData(null, JSON.stringify([{ finder.parseData(null, JSON.stringify([{
@ -200,7 +206,7 @@ describe('find-visualstudio', function () {
}) })
}) })
it('single unusable VS2017', function () { it('single unusable VS2017', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, null) const finder = new TestVisualStudioFinder(semverV1, null, null)
const file = path.join(__dirname, 'fixtures', 'VS_2017_Unusable.txt') const file = path.join(__dirname, 'fixtures', 'VS_2017_Unusable.txt')
@ -212,204 +218,197 @@ describe('find-visualstudio', function () {
}) })
}) })
it('minimal VS2017 Build Tools', function () { it('minimal VS2017 Build Tools', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\' +
'BuildTools\\MSBuild\\15.0\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools',
sdk: '10.0.17134.0',
toolset: 'v141',
version: '15.9.28307.665',
versionMajor: 15,
versionMinor: 9,
versionYear: 2017
})
})
poison(finder, 'regSearchKeys') poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => { finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures', const file = path.join(__dirname, 'fixtures',
'VS_2017_BuildTools_minimal.txt') 'VS_2017_BuildTools_minimal.txt')
const data = fs.readFileSync(file) const data = fs.readFileSync(file)
finder.parseData(null, data, '', cb) return finder.parseData(null, data, '')
} }
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\' +
'BuildTools\\MSBuild\\15.0\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools',
sdk: '10.0.17134.0',
toolset: 'v141',
version: '15.9.28307.665',
versionMajor: 15,
versionMinor: 9,
versionYear: 2017
})
}) })
it('VS2017 Community with C++ workload', function () { it('VS2017 Community with C++ workload', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\' +
'Community\\MSBuild\\15.0\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community',
sdk: '10.0.17763.0',
toolset: 'v141',
version: '15.9.28307.665',
versionMajor: 15,
versionMinor: 9,
versionYear: 2017
})
})
poison(finder, 'regSearchKeys') poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => { finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures', const file = path.join(__dirname, 'fixtures',
'VS_2017_Community_workload.txt') 'VS_2017_Community_workload.txt')
const data = fs.readFileSync(file) const data = fs.readFileSync(file)
finder.parseData(null, data, '', cb) return finder.parseData(null, data, '')
} }
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\' +
'Community\\MSBuild\\15.0\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community',
sdk: '10.0.17763.0',
toolset: 'v141',
version: '15.9.28307.665',
versionMajor: 15,
versionMinor: 9,
versionYear: 2017
})
}) })
it('VS2017 Express', function () { it('VS2017 Express', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\' +
'WDExpress\\MSBuild\\15.0\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\WDExpress',
sdk: '10.0.17763.0',
toolset: 'v141',
version: '15.9.28307.858',
versionMajor: 15,
versionMinor: 9,
versionYear: 2017
})
})
poison(finder, 'regSearchKeys') poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => { finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures', 'VS_2017_Express.txt') const file = path.join(__dirname, 'fixtures', 'VS_2017_Express.txt')
const data = fs.readFileSync(file) const data = fs.readFileSync(file)
finder.parseData(null, data, '', cb) return finder.parseData(null, data, '')
} }
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\' +
'WDExpress\\MSBuild\\15.0\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\WDExpress',
sdk: '10.0.17763.0',
toolset: 'v141',
version: '15.9.28307.858',
versionMajor: 15,
versionMinor: 9,
versionYear: 2017
})
}) })
it('VS2019 Preview with C++ workload', function () { it('VS2019 Preview with C++ workload', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\' +
'Preview\\MSBuild\\Current\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Preview',
sdk: '10.0.17763.0',
toolset: 'v142',
version: '16.0.28608.199',
versionMajor: 16,
versionMinor: 0,
versionYear: 2019
})
})
poison(finder, 'regSearchKeys') poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => { finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures', const file = path.join(__dirname, 'fixtures',
'VS_2019_Preview.txt') 'VS_2019_Preview.txt')
const data = fs.readFileSync(file) const data = fs.readFileSync(file)
finder.parseData(null, data, '', cb) return finder.parseData(null, data, '')
} }
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\' +
'Preview\\MSBuild\\Current\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Preview',
sdk: '10.0.17763.0',
toolset: 'v142',
version: '16.0.28608.199',
versionMajor: 16,
versionMinor: 0,
versionYear: 2019
})
}) })
it('minimal VS2019 Build Tools', function () { it('minimal VS2019 Build Tools', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\' +
'BuildTools\\MSBuild\\Current\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools',
sdk: '10.0.17134.0',
toolset: 'v142',
version: '16.1.28922.388',
versionMajor: 16,
versionMinor: 1,
versionYear: 2019
})
})
poison(finder, 'regSearchKeys') poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => { finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures', const file = path.join(__dirname, 'fixtures',
'VS_2019_BuildTools_minimal.txt') 'VS_2019_BuildTools_minimal.txt')
const data = fs.readFileSync(file) const data = fs.readFileSync(file)
finder.parseData(null, data, '', cb) return finder.parseData(null, data, '')
} }
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\' +
'BuildTools\\MSBuild\\Current\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools',
sdk: '10.0.17134.0',
toolset: 'v142',
version: '16.1.28922.388',
versionMajor: 16,
versionMinor: 1,
versionYear: 2019
})
}) })
it('VS2019 Community with C++ workload', function () { it('VS2019 Community with C++ workload', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\' +
'Community\\MSBuild\\Current\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community',
sdk: '10.0.17763.0',
toolset: 'v142',
version: '16.1.28922.388',
versionMajor: 16,
versionMinor: 1,
versionYear: 2019
})
})
poison(finder, 'regSearchKeys') poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => { finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures', const file = path.join(__dirname, 'fixtures',
'VS_2019_Community_workload.txt') 'VS_2019_Community_workload.txt')
const data = fs.readFileSync(file) const data = fs.readFileSync(file)
finder.parseData(null, data, '', cb) return finder.parseData(null, data, '')
} }
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\' +
'Community\\MSBuild\\Current\\Bin\\MSBuild.exe',
path:
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community',
sdk: '10.0.17763.0',
toolset: 'v142',
version: '16.1.28922.388',
versionMajor: 16,
versionMinor: 1,
versionYear: 2019
})
}) })
it('VS2022 Preview with C++ workload', function () { it('VS2022 Preview with C++ workload', async function () {
const msBuildPath = process.arch === 'arm64' const msBuildPath = process.arch === 'arm64'
? 'C:\\Program Files\\Microsoft Visual Studio\\2022\\' + ? 'C:\\Program Files\\Microsoft Visual Studio\\2022\\' +
'Community\\MSBuild\\Current\\Bin\\arm64\\MSBuild.exe' 'Community\\MSBuild\\Current\\Bin\\arm64\\MSBuild.exe'
: 'C:\\Program Files\\Microsoft Visual Studio\\2022\\' + : 'C:\\Program Files\\Microsoft Visual Studio\\2022\\' +
'Community\\MSBuild\\Current\\Bin\\MSBuild.exe' 'Community\\MSBuild\\Current\\Bin\\MSBuild.exe'
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: msBuildPath,
path:
'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community',
sdk: '10.0.22621.0',
toolset: 'v143',
version: '17.4.33213.308',
versionMajor: 17,
versionMinor: 4,
versionYear: 2022
})
})
poison(finder, 'regSearchKeys') poison(finder, 'regSearchKeys')
finder.msBuildPathExists = (path) => { finder.msBuildPathExists = (path) => {
return true return true
} }
finder.findVisualStudio2017OrNewer = (cb) => { finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures', const file = path.join(__dirname, 'fixtures',
'VS_2022_Community_workload.txt') 'VS_2022_Community_workload.txt')
const data = fs.readFileSync(file) const data = fs.readFileSync(file)
finder.parseData(null, data, '', cb) return finder.parseData(null, data, '')
} }
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info, {
msBuild: msBuildPath,
path:
'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community',
sdk: '10.0.22621.0',
toolset: 'v143',
version: '17.4.33213.308',
versionMajor: 17,
versionMinor: 4,
versionYear: 2022
})
}) })
function allVsVersions (finder) { function allVsVersions (finder) {
finder.findVisualStudio2017OrNewer = (cb) => { finder.findVisualStudio2017OrNewer = async () => {
const data0 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures', const data0 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
'VS_2017_Unusable.txt'))) 'VS_2017_Unusable.txt')))
const data1 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures', const data1 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
@ -428,9 +427,9 @@ describe('find-visualstudio', function () {
'VS_2022_Community_workload.txt'))) 'VS_2022_Community_workload.txt')))
const data = JSON.stringify(data0.concat(data1, data2, data3, data4, const data = JSON.stringify(data0.concat(data1, data2, data3, data4,
data5, data6, data7)) data5, data6, data7))
finder.parseData(null, data, '', cb) return finder.parseData(null, data, '')
} }
finder.regSearchKeys = (keys, value, addOpts, cb) => { finder.regSearchKeys = async (keys, value, addOpts) => {
for (let i = 0; i < keys.length; ++i) { for (let i = 0; i < keys.length; ++i) {
const fullName = `${keys[i]}\\${value}` const fullName = `${keys[i]}\\${value}`
switch (fullName) { switch (fullName) {
@ -438,225 +437,201 @@ describe('find-visualstudio', function () {
case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0': case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0':
continue continue
case 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0': case 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0':
return cb(null, 'C:\\VS2013\\VC\\') return 'C:\\VS2013\\VC\\'
case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\12.0\\MSBuildToolsPath': case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\12.0\\MSBuildToolsPath':
return cb(null, 'C:\\MSBuild12\\') return 'C:\\MSBuild12\\'
case 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0': case 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0':
return cb(null, 'C:\\VS2015\\VC\\') return 'C:\\VS2015\\VC\\'
case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\14.0\\MSBuildToolsPath': case 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions\\14.0\\MSBuildToolsPath':
return cb(null, 'C:\\MSBuild14\\') return 'C:\\MSBuild14\\'
default: default:
assert.fail(`unexpected search for registry value ${fullName}`) assert.fail(`unexpected search for registry value ${fullName}`)
} }
} }
return cb(new Error()) throw new Error()
} }
} }
it('fail when looking for invalid path', function () { it('fail when looking for invalid path', async function () {
const finder = new TestVisualStudioFinder(semverV1, 'AABB', (err, info) => { const finder = new TestVisualStudioFinder(semverV1, 'AABB')
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
}) })
it('look for VS2013 by version number', function () { it('look for VS2013 by version number', async function () {
const finder = new TestVisualStudioFinder(semverV1, '2013', (err, info) => { const finder = new TestVisualStudioFinder(semverV1, '2013')
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2013)
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2013)
}) })
it('look for VS2013 by installation path', function () { it('look for VS2013 by installation path', async function () {
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2013', const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2013')
(err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2013')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2013')
}) })
it('look for VS2015 by version number', function () { it('look for VS2015 by version number', async function () {
const finder = new TestVisualStudioFinder(semverV1, '2015', (err, info) => { const finder = new TestVisualStudioFinder(semverV1, '2015')
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2015)
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2015)
}) })
it('look for VS2015 by installation path', function () { it('look for VS2015 by installation path', async function () {
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015', const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015')
(err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2015')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2015')
}) })
it('look for VS2017 by version number', function () { it('look for VS2017 by version number', async function () {
const finder = new TestVisualStudioFinder(semverV1, '2017', (err, info) => { const finder = new TestVisualStudioFinder(semverV1, '2017')
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2017)
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2017)
}) })
it('look for VS2017 by installation path', function () { it('look for VS2017 by installation path', async function () {
const finder = new TestVisualStudioFinder(semverV1, const finder = new TestVisualStudioFinder(semverV1,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community', 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community')
(err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community')
}) })
it('look for VS2019 by version number', function () { it('look for VS2019 by version number', async function () {
const finder = new TestVisualStudioFinder(semverV1, '2019', (err, info) => { const finder = new TestVisualStudioFinder(semverV1, '2019')
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2019)
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2019)
}) })
it('look for VS2019 by installation path', function () { it('look for VS2019 by installation path', async function () {
const finder = new TestVisualStudioFinder(semverV1, const finder = new TestVisualStudioFinder(semverV1,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools', 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
(err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
}) })
it('look for VS2022 by version number', function () { it('look for VS2022 by version number', async function () {
const finder = new TestVisualStudioFinder(semverV1, '2022', (err, info) => { const finder = new TestVisualStudioFinder(semverV1, '2022')
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2022)
})
finder.msBuildPathExists = (path) => { finder.msBuildPathExists = (path) => {
return true return true
} }
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2022)
}) })
it('msvs_version match should be case insensitive', function () { it('msvs_version match should be case insensitive', async function () {
const finder = new TestVisualStudioFinder(semverV1, const finder = new TestVisualStudioFinder(semverV1,
'c:\\program files (x86)\\microsoft visual studio\\2019\\BUILDTOOLS', 'c:\\program files (x86)\\microsoft visual studio\\2019\\BUILDTOOLS')
(err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
}) })
it('latest version should be found by default', function () { it('latest version should be found by default', async function () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2022)
})
finder.msBuildPathExists = (path) => { finder.msBuildPathExists = (path) => {
return true return true
} }
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2022)
}) })
it('run on a usable VS Command Prompt', function () { it('run on a usable VS Command Prompt', async function () {
process.env.VCINSTALLDIR = 'C:\\VS2015\\VC' process.env.VCINSTALLDIR = 'C:\\VS2015\\VC'
// VSINSTALLDIR is not defined on Visual C++ Build Tools 2015 // VSINSTALLDIR is not defined on Visual C++ Build Tools 2015
delete process.env.VSINSTALLDIR delete process.env.VSINSTALLDIR
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2015')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2015')
}) })
it('VCINSTALLDIR match should be case insensitive', function () { it('VCINSTALLDIR match should be case insensitive', async function () {
process.env.VCINSTALLDIR = process.env.VCINSTALLDIR =
'c:\\program files (x86)\\microsoft visual studio\\2019\\BUILDTOOLS\\VC' 'c:\\program files (x86)\\microsoft visual studio\\2019\\BUILDTOOLS\\VC'
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
}) })
it('run on a unusable VS Command Prompt', function () { it('run on a unusable VS Command Prompt', async function () {
process.env.VCINSTALLDIR = process.env.VCINSTALLDIR =
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildToolsUnusable\\VC' 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildToolsUnusable\\VC'
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => { const finder = new TestVisualStudioFinder(semverV1, null)
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
}) })
it('run on a VS Command Prompt with matching msvs_version', function () { it('run on a VS Command Prompt with matching msvs_version', async function () {
process.env.VCINSTALLDIR = 'C:\\VS2015\\VC' process.env.VCINSTALLDIR = 'C:\\VS2015\\VC'
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015', const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015')
(err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2015')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2015')
}) })
it('run on a VS Command Prompt with mismatched msvs_version', function () { it('run on a VS Command Prompt with mismatched msvs_version', async function () {
process.env.VCINSTALLDIR = process.env.VCINSTALLDIR =
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC' 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC'
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015', const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015')
(err, info) => {
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
})
allVsVersions(finder) allVsVersions(finder)
finder.findVisualStudio() const { err, info } = await findVisualStudio(finder)
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
}) })
}) })

View file

@ -1,52 +1,56 @@
'use strict' 'use strict'
const { describe, it, after } = require('mocha') const { describe, it, after } = require('mocha')
const { rm } = require('fs/promises') const { rm, mkdtemp } = require('fs/promises')
const { createWriteStream } = require('fs')
const assert = require('assert') const assert = require('assert')
const path = require('path') const path = require('path')
const os = require('os') const os = require('os')
const util = require('util')
const { test: { download, install } } = require('../lib/install')
const gyp = require('../lib/node-gyp')
const semver = require('semver') const semver = require('semver')
const stream = require('stream') const { pipeline: streamPipeline } = require('stream/promises')
const streamPipeline = util.promisify(stream.pipeline) const requireInject = require('require-inject')
const gyp = require('../lib/node-gyp')
const createInstall = (mocks = {}) => requireInject('../lib/install', mocks).test
const { download, install } = createInstall()
describe('install', function () { describe('install', function () {
it('EACCES retry once', async () => { it('EACCES retry once', async () => {
const fs = { let statCalled = 0
promises: { const mockInstall = createInstall({
stat (_) { 'graceful-fs': {
const err = new Error() promises: {
err.code = 'EACCES' stat (_) {
assert.ok(true) const err = new Error()
throw err err.code = 'EACCES'
statCalled++
throw err
}
} }
} }
} })
const Gyp = { const Gyp = {
devDir: __dirname, devDir: __dirname,
opts: { opts: {
ensure: true ensure: true
}, },
commands: { commands: {
install (argv, cb) { install: (...args) => mockInstall.install(Gyp, ...args),
install(fs, Gyp, argv).then(cb, cb) remove: async () => {}
},
remove (_, cb) {
cb()
}
} }
} }
let err
try { try {
await install(fs, Gyp, []) await Gyp.commands.install([])
} catch (err) { } catch (e) {
err = e
}
assert.ok(err)
assert.equal(statCalled, 2)
if (/"pre" versions of node cannot be installed/.test(err.message)) {
assert.ok(true) assert.ok(true)
if (/"pre" versions of node cannot be installed/.test(err.message)) {
assert.ok(true)
}
} }
}) })
@ -56,7 +60,7 @@ describe('install', function () {
semver.prerelease(process.version) !== null || semver.prerelease(process.version) !== null ||
semver.satisfies(process.version, '<10') semver.satisfies(process.version, '<10')
async function parallelInstallsTest (test, fs, devDir, prog) { async function parallelInstallsTest (test, devDir, prog) {
if (skipParallelInstallTests) { if (skipParallelInstallTests) {
return test.skip('Skipping parallel installs test due to test environment configuration') return test.skip('Skipping parallel installs test due to test environment configuration')
} }
@ -69,52 +73,49 @@ describe('install', function () {
await rm(expectedDir, { recursive: true, force: true }) await rm(expectedDir, { recursive: true, force: true })
await Promise.all([ await Promise.all([
install(fs, prog, []), install(prog, []),
install(fs, prog, []), install(prog, []),
install(fs, prog, []), install(prog, []),
install(fs, prog, []), install(prog, []),
install(fs, prog, []), install(prog, []),
install(fs, prog, []), install(prog, []),
install(fs, prog, []), install(prog, []),
install(fs, prog, []), install(prog, []),
install(fs, prog, []), install(prog, []),
install(fs, prog, []) install(prog, [])
]) ])
} }
it('parallel installs (ensure=true)', async function () { it('parallel installs (ensure=true)', async function () {
this.timeout(600000) this.timeout(600000)
const fs = require('graceful-fs') const devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-'))
const devDir = await util.promisify(fs.mkdtemp)(path.join(os.tmpdir(), 'node-gyp-test-'))
const prog = gyp() const prog = gyp()
prog.parseArgv([]) prog.parseArgv([])
prog.devDir = devDir prog.devDir = devDir
prog.opts.ensure = true prog.opts.ensure = true
await parallelInstallsTest(this, fs, devDir, prog) await parallelInstallsTest(this, devDir, prog)
}) })
it('parallel installs (ensure=false)', async function () { it('parallel installs (ensure=false)', async function () {
this.timeout(600000) this.timeout(600000)
const fs = require('graceful-fs') const devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-'))
const devDir = await util.promisify(fs.mkdtemp)(path.join(os.tmpdir(), 'node-gyp-test-'))
const prog = gyp() const prog = gyp()
prog.parseArgv([]) prog.parseArgv([])
prog.devDir = devDir prog.devDir = devDir
prog.opts.ensure = false prog.opts.ensure = false
await parallelInstallsTest(this, fs, devDir, prog) await parallelInstallsTest(this, devDir, prog)
}) })
it('parallel installs (tarball)', async function () { it('parallel installs (tarball)', async function () {
this.timeout(600000) this.timeout(600000)
const fs = require('graceful-fs') const devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-'))
const devDir = await util.promisify(fs.mkdtemp)(path.join(os.tmpdir(), 'node-gyp-test-'))
const prog = gyp() const prog = gyp()
prog.parseArgv([]) prog.parseArgv([])
@ -123,9 +124,9 @@ describe('install', function () {
await streamPipeline( await streamPipeline(
(await download(prog, `https://nodejs.org/dist/${process.version}/node-${process.version}.tar.gz`)).body, (await download(prog, `https://nodejs.org/dist/${process.version}/node-${process.version}.tar.gz`)).body,
fs.createWriteStream(prog.opts.tarball) createWriteStream(prog.opts.tarball)
) )
await parallelInstallsTest(this, fs, devDir, prog) await parallelInstallsTest(this, devDir, prog)
}) })
}) })