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()
if (!command) {
// done!
@ -77,30 +77,28 @@ function run () {
return
}
prog.commands[command.name](command.args, function (err) {
if (err) {
log.error(command.name + ' error')
log.error('stack', err.stack)
errorMessage()
log.error('not ok')
return process.exit(1)
}
try {
const args = await prog.commands[command.name](command.args) ?? []
if (command.name === 'list') {
const versions = arguments[1]
if (versions.length > 0) {
versions.forEach(function (version) {
console.log(version)
})
if (args.length) {
args.forEach((version) => console.log(version))
} else {
console.log('No node development files installed. Use `node-gyp install` to install a version.')
}
} else if (arguments.length >= 2) {
console.log.apply(console, [].slice.call(arguments, 1))
} else if (args.length >= 1) {
console.log(...args.slice(1))
}
// 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) {

View file

@ -202,13 +202,7 @@ async function build (gyp, argv) {
await new Promise((resolve, reject) => proc.on('exit', async (code, signal) => {
if (buildBinsDir) {
// Clean up the build-time dependency symlinks:
if (fs.rm) {
// 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 })
}
await fs.rm(buildBinsDir, { recursive: true })
}
if (code !== 0) {
@ -222,7 +216,5 @@ async function build (gyp, argv) {
}
}
module.exports = function (gyp, argv, callback) {
build(gyp, argv).then(callback.bind(undefined, null), callback)
}
module.exports = build
module.exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module'

View file

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

View file

@ -1,6 +1,6 @@
'use strict'
const fs = require('graceful-fs')
const { openSync, closeSync, promises: fs } = require('graceful-fs')
const path = require('path')
const log = require('./log')
const os = require('os')
@ -8,40 +8,28 @@ const processRelease = require('./process-release')
const win = process.platform === 'win32'
const findNodeDirectory = require('./find-node-directory')
const createConfigGypi = require('./create-config-gypi')
const msgFormat = require('util').format
const { format: msgFormat } = require('util')
const findPython = require('./find-python')
let findVisualStudio
if (win) {
findVisualStudio = require('./find-visualstudio')
}
const findVisualStudio = win ? require('./find-visualstudio') : null
function configure (gyp, argv, callback) {
let python
async function configure (gyp, argv) {
const buildDir = path.resolve('build')
const configNames = ['config.gypi', 'common.gypi']
const configs = []
let nodeDir
const release = processRelease(argv, gyp, process.version, process.release)
findPython(gyp.opts.python, function (err, found) {
if (err) {
callback(err)
} else {
python = found
getNodeDir()
}
})
const python = await findPython(gyp.opts.python)
return getNodeDir()
function getNodeDir () {
async function getNodeDir () {
// 'python' should be set by now
process.env.PYTHON = python
if (gyp.opts.nodedir) {
// --nodedir was specified. use that for the dev files
nodeDir = gyp.opts.nodedir.replace(/^~/, os.homedir())
log.verbose('get node dir', 'compiling against specified --nodedir dev files: %s', nodeDir)
createBuildDir()
} else {
// if no --nodedir specified, ensure node dependencies are installed
if ('v' + release.version !== process.version) {
@ -54,87 +42,66 @@ function configure (gyp, argv, callback) {
if (!release.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
// into devdir. Otherwise only install if they're not already there.
gyp.opts.ensure = !gyp.opts.tarball
gyp.commands.install([release.version], function (err) {
if (err) {
return callback(err)
}
log.verbose('get node dir', 'target node version installed:', release.versionDir)
nodeDir = path.resolve(gyp.devDir, release.versionDir)
createBuildDir()
})
await gyp.commands.install([release.version])
log.verbose('get node dir', 'target node version installed:', release.versionDir)
nodeDir = path.resolve(gyp.devDir, release.versionDir)
}
return createBuildDir()
}
function createBuildDir () {
async function createBuildDir () {
log.verbose('build dir', 'attempting to create "build" dir: %s', buildDir)
fs.mkdir(buildDir, { recursive: true }, function (err, isNew) {
if (err) {
return callback(err)
}
log.verbose(
'build dir', '"build" dir needed to be created?', isNew ? 'Yes' : 'No'
)
if (win) {
findVisualStudio(release.semver, gyp.opts['msvs-version'],
createConfigFile)
} else {
createConfigFile()
}
})
const isNew = await fs.mkdir(buildDir, { recursive: true })
log.verbose(
'build dir', '"build" dir needed to be created?', isNew ? 'Yes' : 'No'
)
const vsInfo = win ? await findVisualStudio(release.semver, gyp.opts['msvs-version']) : null
return createConfigFile(vsInfo)
}
function createConfigFile (err, vsInfo) {
if (err) {
return callback(err)
}
if (process.platform === 'win32') {
async function createConfigFile (vsInfo) {
if (win) {
process.env.GYP_MSVS_VERSION = Math.min(vsInfo.versionYear, 2015)
process.env.GYP_MSVS_OVERRIDE_PATH = vsInfo.path
}
createConfigGypi({ gyp, buildDir, nodeDir, vsInfo, python }).then(configPath => {
configs.push(configPath)
findConfigs()
}).catch(err => {
callback(err)
})
const configPath = await createConfigGypi({ gyp, buildDir, nodeDir, vsInfo, python })
configs.push(configPath)
return findConfigs()
}
function findConfigs () {
async function findConfigs () {
const name = configNames.shift()
if (!name) {
return runGyp()
}
const fullPath = path.resolve(name)
log.verbose(name, 'checking for gypi file: %s', fullPath)
fs.stat(fullPath, function (err) {
if (err) {
if (err.code === 'ENOENT') {
findConfigs() // check next gypi filename
} else {
callback(err)
}
} else {
log.verbose(name, 'found gypi file')
configs.push(fullPath)
findConfigs()
try {
await fs.stat(fullPath)
log.verbose(name, 'found gypi file')
configs.push(fullPath)
} catch (err) {
// ENOENT will check next gypi filename
if (err.code !== 'ENOENT') {
throw err
}
})
}
function runGyp (err) {
if (err) {
return callback(err)
}
return findConfigs()
}
async function runGyp () {
if (!~argv.indexOf('-f') && !~argv.indexOf('--format')) {
if (win) {
log.verbose('gyp', 'gyp format was not specified; forcing "msvs"')
@ -189,7 +156,7 @@ function configure (gyp, argv, callback) {
} else {
const msg = msgFormat('Could not find node.%s file in %s', ext, nodeRootDir)
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) {
// zoslib is only shipped in Node v16 and above.
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 addonGypi = path.resolve(__dirname, '..', 'addon.gypi')
let commonGypi = path.resolve(nodeDir, 'include/node/common.gypi')
fs.stat(commonGypi, function (err) {
if (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()
try {
await fs.stat(commonGypi)
} 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 ? ';' : ':')
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])
let fd
try {
fd = fs.openSync(candidate, 'r')
fd = openSync(candidate, 'r')
} catch (e) {
// this candidate was not found or not readable, do nothing
log.silly(logprefix, 'Could not open %s: %s', candidate, e.message)
continue
}
fs.closeSync(fd)
closeSync(fd)
log.silly(logprefix, 'Found readable %s', candidate)
return candidate
}

View file

@ -1,6 +1,6 @@
'use strict'
const fs = require('graceful-fs')
const fs = require('graceful-fs').promises
const log = require('./log')
const path = require('path')
@ -24,7 +24,7 @@ async function getBaseConfigGypi ({ gyp, nodeDir }) {
if (shouldReadConfigGypi && nodeDir) {
try {
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())
} catch (err) {
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)
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
}

View file

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

View file

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

View file

@ -1,26 +1,20 @@
'use strict'
const fs = require('graceful-fs')
const { createWriteStream, promises: fs } = require('graceful-fs')
const os = require('os')
const { backOff } = require('exponential-backoff')
const rm = require('rimraf')
const tar = require('tar')
const path = require('path')
const util = require('util')
const stream = require('stream')
const { Transform, promises: { pipeline } } = require('stream')
const crypto = require('crypto')
const log = require('./log')
const semver = require('semver')
const fetch = require('make-fetch-happen')
const processRelease = require('./process-release')
const win = process.platform === 'win32'
const streamPipeline = util.promisify(stream.pipeline)
/**
* @param {typeof import('graceful-fs')} fs
*/
async function install (fs, gyp, argv) {
async function install (gyp, argv) {
console.log()
const release = processRelease(argv, gyp, process.version, process.release)
// 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') : ''
@ -60,7 +54,7 @@ async function install (fs, gyp, argv) {
if (gyp.opts.ensure) {
log.verbose('install', '--ensure was passed, so won\'t reinstall if already installed')
try {
await fs.promises.stat(devDir)
await fs.stat(devDir)
} catch (err) {
if (err.code === 'ENOENT') {
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')
let installVersion = 0
try {
const ver = await fs.promises.readFile(installVersionFile, 'ascii')
const ver = await fs.readFile(installVersionFile, 'ascii')
installVersion = parseInt(ver, 10) || 0
} catch (err) {
if (err.code !== 'ENOENT') {
@ -100,7 +94,7 @@ async function install (fs, gyp, argv) {
log.verbose('on Windows; need to check node.lib')
const nodeLibPath = path.resolve(devDir, arch, 'node.lib')
try {
await fs.promises.stat(nodeLibPath)
await fs.stat(nodeLibPath)
} catch (err) {
if (err.code === 'ENOENT') {
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) {
try {
await fs.promises.stat(src)
await fs.stat(src)
} catch {
throw new Error(`Missing source directory for copy: ${src}`)
}
await fs.promises.mkdir(dest, { recursive: true })
const entries = await fs.promises.readdir(src, { withFileTypes: true })
await fs.mkdir(dest, { recursive: true })
const entries = await fs.readdir(src, { withFileTypes: true })
for (const entry of entries) {
if (entry.isDirectory()) {
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
await backOff(async () => {
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) {
// if ensure, check if file already exists and that's good enough
if (gyp.opts.ensure && err.code === 'EBUSY') {
try {
await fs.promises.stat(path.join(dest, entry.name))
await fs.stat(path.join(dest, entry.name))
return
} catch {}
}
@ -163,7 +157,7 @@ async function install (fs, gyp, argv) {
// first create the dir for the node dev files
try {
const created = await fs.promises.mkdir(devDir, { recursive: true })
const created = await fs.mkdir(devDir, { recursive: true })
if (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
// are happening (not uncommon with multiple native modules) so
// 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 {
if (shouldDownloadTarball) {
@ -228,7 +222,7 @@ async function install (fs, gyp, argv) {
throw new Error(`${res.status} response downloading ${release.tarballUrl}`)
}
await streamPipeline(
await pipeline(
res.body,
// content checksum
new ShaSum((_, checksum) => {
@ -267,7 +261,7 @@ async function install (fs, gyp, argv) {
// need to download node.lib
...(win ? [downloadNodeLib()] : []),
// 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
...(!tarPath || win ? [downloadShasums()] : [])
])
@ -289,7 +283,7 @@ async function install (fs, gyp, argv) {
if (tarExtractDir !== devDir) {
try {
// try to cleanup temp dir
await util.promisify(rm)(tarExtractDir)
await fs.rm(tarExtractDir, { recursive: true })
} catch {
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, 'url', libUrl)
await fs.promises.mkdir(dir, { recursive: true })
await fs.mkdir(dir, { recursive: true })
log.verbose('streaming', name, 'to:', targetLibPath)
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}`)
}
return streamPipeline(
return pipeline(
res.body,
new ShaSum((_, checksum) => {
contentShasums[libPath] = checksum
log.verbose('content checksum', libPath, checksum)
}),
fs.createWriteStream(targetLibPath)
createWriteStream(targetLibPath)
)
} // downloadNodeLib()
} // go()
@ -363,7 +357,7 @@ async function install (fs, gyp, argv) {
async function rollback (err) {
log.warn('install', 'got an error, rolling back install')
// roll-back the install if anything went wrong
await util.promisify(gyp.commands.remove)([release.versionDir])
await gyp.commands.remove([release.versionDir])
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')
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) {
super()
this._callback = callback
@ -442,14 +436,12 @@ async function download (gyp, url) {
async function readCAFile (filename) {
// The CA file can contain multiple certificates so split on certificate
// 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
return ca.match(re)
}
module.exports = function (gyp, argv, callback) {
install(fs, gyp, argv).then(callback.bind(undefined, null), callback)
}
module.exports = install
module.exports.test = {
download,
install,

View file

@ -1,26 +1,25 @@
'use strict'
const fs = require('graceful-fs')
const fs = require('graceful-fs').promises
const log = require('./log')
function list (gyp, args, callback) {
async function list (gyp, args) {
const devDir = gyp.devDir
log.verbose('list', 'using node-gyp dir:', devDir)
fs.readdir(devDir, onreaddir)
function onreaddir (err, versions) {
let versions = []
try {
const dir = await fs.readdir(devDir)
if (Array.isArray(dir)) {
versions = dir.filter((v) => v !== 'current')
}
} catch (err) {
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

View file

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

View file

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

View file

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

View file

@ -1,50 +1,55 @@
'use strict'
const log = require('./log')
const execFile = require('child_process').execFile
const cp = require('child_process')
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 outRe = new RegExp(`^\\s+${outReValue}\\s+REG_\\w+\\s+(\\S.*)$`, 'im')
const reg = path.join(process.env.SystemRoot, 'System32', 'reg.exe')
const regArgs = ['query', key, '/v', value].concat(addOpts)
log.silly('reg', 'running', reg, regArgs)
const child = 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 [err, stdout, stderr] = await execFile(reg, regArgs, { encoding: 'utf8' })
const result = outRe.exec(stdout)
if (!result) {
log.silly('reg', 'error parsing stdout')
return cb(new Error('Could not parse output of reg.exe'))
}
log.silly('reg', 'found: %j', result[1])
cb(null, result[1])
})
child.stdin.end()
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)
if (err) {
throw err
}
throw new Error(stderr)
}
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) {
let i = 0
const search = () => {
log.silly('reg-search', 'looking for %j in %j', value, keys[i])
regGetValue(keys[i], value, addOpts, (err, res) => {
++i
if (err && i < keys.length) { return search() }
cb(err, res)
})
async function regSearchKeys (keys, value, addOpts) {
for (const key of keys) {
try {
return await regGetValue(key, value, addOpts)
} catch {
continue
}
}
search()
}
module.exports = {
execFile,
regGetValue,
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 path = require('path')
const fs = require('graceful-fs')
const childProcess = require('child_process')
const { execFileSync, execFile } = require('child_process')
const os = require('os')
const addonPath = path.resolve(__dirname, 'node_modules', 'hello_world')
const nodeGyp = path.resolve(__dirname, '..', 'bin', 'node-gyp.js')
const execFileSync = childProcess.execFileSync || require('./process-exec-sync')
const execFile = childProcess.execFile
function runHello (hostProcess) {
if (!hostProcess) {

View file

@ -9,13 +9,12 @@ const log = require('../lib/log')
const requireInject = require('require-inject')
const configure = requireInject('../lib/configure', {
'graceful-fs': {
openSync: function () { return 0 },
closeSync: function () { },
writeFile: function (file, data, cb) { cb() },
stat: function (file, cb) { cb(null, {}) },
mkdir: function (dir, options, cb) { cb() },
openSync: () => 0,
closeSync: () => {},
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 fs = require('fs/promises')
const path = require('path')
const util = require('util')
const http = require('http')
const https = require('https')
const install = require('../lib/install')
@ -176,7 +175,7 @@ describe('download', function () {
prog.parseArgv([])
prog.devDir = devDir
log.level = 'warn'
await util.promisify(install)(prog, [])
await install(prog, [])
const data = await fs.readFile(path.join(expectedDir, 'installVersion'), 'utf8')
assert.strictEqual(data, '11\n', 'correct installVersion')

View file

@ -4,22 +4,16 @@ delete process.env.PYTHON
const { describe, it } = require('mocha')
const assert = require('assert')
const findPython = require('../lib/find-python')
const execFile = require('child_process').execFile
const PythonFinder = findPython.test.PythonFinder
const { test: { PythonFinder, findPython: testFindPython } } = require('../lib/find-python')
const { execFile } = require('../lib/util')
describe('find-python', function () {
it('find python', function () {
findPython.test.findPython(null, function (err, found) {
assert.strictEqual(err, null)
const proc = execFile(found, ['-V'], function (err, stdout, stderr) {
assert.strictEqual(err, null)
assert.ok(/Python 3/.test(stdout))
assert.strictEqual(stderr, '')
})
proc.stdout.setEncoding('utf-8')
proc.stderr.setEncoding('utf-8')
})
it('find python', async function () {
const found = await testFindPython(null)
const [err, stdout, stderr] = await execFile(found, ['-V'], { encoding: 'utf-8' })
assert.strictEqual(err, null)
assert.ok(/Python 3/.test(stdout))
assert.strictEqual(stderr, '')
})
function poison (object, property) {
@ -36,109 +30,105 @@ describe('find-python', function () {
Object.defineProperty(object, property, descriptor)
}
function TestPythonFinder () {
PythonFinder.apply(this, arguments)
}
function TestPythonFinder () { PythonFinder.apply(this, arguments) }
TestPythonFinder.prototype = Object.create(PythonFinder.prototype)
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 () {
const f = new TestPythonFinder('python', done)
f.execFile = function (program, args, opts, cb) {
f.execFile = function (program, args, opts, cb) {
it('find python - python', async function () {
const f = new TestPythonFinder('python')
f.execFile = async function (program, args, opts) {
f.execFile = async function (program, args, opts) {
poison(f, 'execFile')
assert.strictEqual(program, '/path/python')
assert.ok(/sys\.version_info/.test(args[1]))
cb(null, '3.9.1')
return [null, '3.9.1']
}
assert.strictEqual(program,
process.platform === 'win32' ? '"python"' : 'python')
assert.strictEqual(program, process.platform === 'win32' ? '"python"' : 'python')
assert.ok(/sys\.executable/.test(args[1]))
cb(null, '/path/python')
return [null, '/path/python']
}
f.findPython()
function done (err, python) {
assert.strictEqual(err, null)
assert.strictEqual(python, '/path/python')
}
const { err, python } = await findPython(f)
assert.strictEqual(err, null)
assert.strictEqual(python, '/path/python')
})
it('find python - python too old', function () {
const f = new TestPythonFinder(null, done)
f.execFile = function (program, args, opts, cb) {
it('find python - python too old', async function () {
const f = new TestPythonFinder(null)
f.execFile = async function (program, args, opts) {
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])) {
cb(null, '2.3.4')
return [null, '2.3.4']
} else {
assert.fail()
}
}
f.findPython()
function done (err) {
assert.ok(/Could not find any Python/.test(err))
assert.ok(/not supported/i.test(f.errorLog))
}
const { err } = await findPython(f)
assert.ok(/Could not find any Python/.test(err))
assert.ok(/not supported/i.test(f.errorLog))
})
it('find python - no python', function () {
const f = new TestPythonFinder(null, done)
f.execFile = function (program, args, opts, cb) {
it('find python - no python', async function () {
const f = new TestPythonFinder(null)
f.execFile = async function (program, args, opts) {
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])) {
cb(new Error('not a Python executable'))
throw new Error('not a Python executable')
} else {
assert.fail()
}
}
f.findPython()
function done (err) {
assert.ok(/Could not find any Python/.test(err))
assert.ok(/not in PATH/.test(f.errorLog))
}
const { err } = await findPython(f)
assert.ok(/Could not find any Python/.test(err))
assert.ok(/not in PATH/.test(f.errorLog))
})
it('find python - no python2, no python, unix', function () {
const f = new TestPythonFinder(null, done)
it('find python - no python2, no python, unix', async function () {
const f = new TestPythonFinder(null)
f.checkPyLauncher = assert.fail
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])) {
cb(new Error('not found'))
throw new Error('not found')
} else {
assert.fail()
}
}
f.findPython()
function done (err) {
assert.ok(/Could not find any Python/.test(err))
assert.ok(/not in PATH/.test(f.errorLog))
}
const { err } = await findPython(f)
assert.ok(/Could not find any Python/.test(err))
assert.ok(/not in PATH/.test(f.errorLog))
})
it('find python - no python, use python launcher', function () {
const f = new TestPythonFinder(null, done)
it('find python - no python, use python launcher', async function () {
const f = new TestPythonFinder(null)
f.win = true
f.execFile = function (program, args, opts, cb) {
f.execFile = async function (program, args, opts) {
if (program === 'py.exe') {
assert.notStrictEqual(args.indexOf('-3'), -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])) {
cb(new Error('not found'))
throw new Error('not found')
} 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])) {
if (program === 'Z:\\snake.exe') {
cb(null, '3.9.0')
return [null, '3.9.0']
} else {
assert.fail()
}
@ -146,58 +136,49 @@ describe('find-python', function () {
assert.fail()
}
}
f.findPython()
function done (err, python) {
assert.strictEqual(err, null)
assert.strictEqual(python, 'Z:\\snake.exe')
}
const { err, python } = await findPython(f)
assert.strictEqual(err, null)
assert.strictEqual(python, 'Z:\\snake.exe')
})
it('find python - no python, no python launcher, good guess', function () {
const f = new TestPythonFinder(null, done)
it('find python - no python, no python launcher, good guess', async function () {
const f = new TestPythonFinder(null)
f.win = true
const expectedProgram = f.winDefaultLocations[0]
f.execFile = function (program, args, opts, cb) {
f.execFile = async function (program, args, opts) {
if (program === 'py.exe') {
return cb(new Error('not found'))
throw new Error('not found')
}
if (/sys\.executable/.test(args[args.length - 1])) {
cb(new Error('not found'))
throw new Error('not found')
} else if (program === expectedProgram &&
/sys\.version_info/.test(args[args.length - 1])) {
cb(null, '3.7.3')
return [null, '3.7.3']
} else {
assert.fail()
}
}
f.findPython()
function done (err, python) {
assert.strictEqual(err, null)
assert.ok(python === expectedProgram)
}
const { err, python } = await findPython(f)
assert.strictEqual(err, null)
assert.ok(python === expectedProgram)
})
it('find python - no python, no python launcher, bad guess', function () {
const f = new TestPythonFinder(null, done)
it('find python - no python, no python launcher, bad guess', async function () {
const f = new TestPythonFinder(null)
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])) {
cb(new Error('not found'))
throw new Error('not found')
} 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 {
assert.fail()
}
}
f.findPython()
function done (err) {
assert.ok(/Could not find any Python/.test(err))
assert.ok(/not in PATH/.test(f.errorLog))
}
const { err } = await findPython(f)
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 fs = require('fs')
const path = require('path')
const findVisualStudio = require('../lib/find-visualstudio')
const VisualStudioFinder = findVisualStudio.test.VisualStudioFinder
const { test: { VisualStudioFinder } } = require('../lib/find-visualstudio')
const semverV1 = { major: 1, minor: 0, patch: 0 }
@ -28,26 +27,22 @@ function poison (object, property) {
function TestVisualStudioFinder () { VisualStudioFinder.apply(this, arguments) }
TestVisualStudioFinder.prototype = Object.create(VisualStudioFinder.prototype)
describe('find-visualstudio', function () {
it('VS2013', function () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
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
})
})
const findVisualStudio = async (finder) => {
try {
return { err: null, info: await finder.findVisualStudio() }
} catch (err) {
return { err, info: null }
}
}
finder.findVisualStudio2017OrNewer = (cb) => {
finder.parseData(new Error(), '', '', cb)
describe('find-visualstudio', function () {
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) {
const fullName = `${keys[i]}\\${value}`
switch (fullName) {
@ -56,35 +51,44 @@ describe('find-visualstudio', function () {
continue
case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0':
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':
assert.ok(true, `expected search for registry value ${fullName}`)
return cb(null, 'C:\\MSBuild12\\')
return 'C:\\MSBuild12\\'
default:
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({
major: 10,
minor: 0,
patch: 0
}, null, (err, info) => {
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
})
}, null)
finder.findVisualStudio2017OrNewer = (cb) => {
finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures', 'VS_2017_Unusable.txt')
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) {
const fullName = `${keys[i]}\\${value}`
switch (fullName) {
@ -95,49 +99,51 @@ describe('find-visualstudio', function () {
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 () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
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('VS2015', async function () {
const finder = new TestVisualStudioFinder(semverV1, null)
finder.findVisualStudio2017OrNewer = (cb) => {
finder.parseData(new Error(), '', '', cb)
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) {
const fullName = `${keys[i]}\\${value}`
switch (fullName) {
case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\14.0':
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':
assert.ok(true, `expected search for registry value ${fullName}`)
return cb(null, 'C:\\MSBuild14\\')
return 'C:\\MSBuild14\\'
default:
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)
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)
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)
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)
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)
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)
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 file = path.join(__dirname, 'fixtures', 'VS_2017_Unusable.txt')
@ -212,204 +218,197 @@ describe('find-visualstudio', function () {
})
})
it('minimal VS2017 Build Tools', function () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
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('minimal VS2017 Build Tools', async function () {
const finder = new TestVisualStudioFinder(semverV1, null)
poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => {
finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures',
'VS_2017_BuildTools_minimal.txt')
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 () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
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 Community with C++ workload', async function () {
const finder = new TestVisualStudioFinder(semverV1, null)
poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => {
finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures',
'VS_2017_Community_workload.txt')
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 () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
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('VS2017 Express', async function () {
const finder = new TestVisualStudioFinder(semverV1, null)
poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => {
finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures', 'VS_2017_Express.txt')
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 () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
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('VS2019 Preview with C++ workload', async function () {
const finder = new TestVisualStudioFinder(semverV1, null)
poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => {
finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures',
'VS_2019_Preview.txt')
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 () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
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('minimal VS2019 Build Tools', async function () {
const finder = new TestVisualStudioFinder(semverV1, null)
poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => {
finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures',
'VS_2019_BuildTools_minimal.txt')
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 () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
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('VS2019 Community with C++ workload', async function () {
const finder = new TestVisualStudioFinder(semverV1, null)
poison(finder, 'regSearchKeys')
finder.findVisualStudio2017OrNewer = (cb) => {
finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures',
'VS_2019_Community_workload.txt')
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'
? 'C:\\Program Files\\Microsoft Visual Studio\\2022\\' +
'Community\\MSBuild\\Current\\Bin\\arm64\\MSBuild.exe'
: 'C:\\Program Files\\Microsoft Visual Studio\\2022\\' +
'Community\\MSBuild\\Current\\Bin\\MSBuild.exe'
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
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
})
})
const finder = new TestVisualStudioFinder(semverV1, null)
poison(finder, 'regSearchKeys')
finder.msBuildPathExists = (path) => {
return true
}
finder.findVisualStudio2017OrNewer = (cb) => {
finder.findVisualStudio2017OrNewer = async () => {
const file = path.join(__dirname, 'fixtures',
'VS_2022_Community_workload.txt')
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) {
finder.findVisualStudio2017OrNewer = (cb) => {
finder.findVisualStudio2017OrNewer = async () => {
const data0 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
'VS_2017_Unusable.txt')))
const data1 = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures',
@ -428,9 +427,9 @@ describe('find-visualstudio', function () {
'VS_2022_Community_workload.txt')))
const data = JSON.stringify(data0.concat(data1, data2, data3, data4,
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) {
const fullName = `${keys[i]}\\${value}`
switch (fullName) {
@ -438,225 +437,201 @@ describe('find-visualstudio', function () {
case 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7\\12.0':
continue
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':
return cb(null, 'C:\\MSBuild12\\')
return 'C:\\MSBuild12\\'
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':
return cb(null, 'C:\\MSBuild14\\')
return 'C:\\MSBuild14\\'
default:
assert.fail(`unexpected search for registry value ${fullName}`)
}
}
return cb(new Error())
throw new Error()
}
}
it('fail when looking for invalid path', function () {
const finder = new TestVisualStudioFinder(semverV1, 'AABB', (err, info) => {
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
})
it('fail when looking for invalid path', async function () {
const finder = new TestVisualStudioFinder(semverV1, 'AABB')
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 () {
const finder = new TestVisualStudioFinder(semverV1, '2013', (err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2013)
})
it('look for VS2013 by version number', async function () {
const finder = new TestVisualStudioFinder(semverV1, '2013')
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 () {
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2013',
(err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2013')
})
it('look for VS2013 by installation path', async function () {
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2013')
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 () {
const finder = new TestVisualStudioFinder(semverV1, '2015', (err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2015)
})
it('look for VS2015 by version number', async function () {
const finder = new TestVisualStudioFinder(semverV1, '2015')
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 () {
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015',
(err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2015')
})
it('look for VS2015 by installation path', async function () {
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015')
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 () {
const finder = new TestVisualStudioFinder(semverV1, '2017', (err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2017)
})
it('look for VS2017 by version number', async function () {
const finder = new TestVisualStudioFinder(semverV1, '2017')
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,
'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')
})
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community')
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 () {
const finder = new TestVisualStudioFinder(semverV1, '2019', (err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2019)
})
it('look for VS2019 by version number', async function () {
const finder = new TestVisualStudioFinder(semverV1, '2019')
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,
'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')
})
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
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 () {
const finder = new TestVisualStudioFinder(semverV1, '2022', (err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2022)
})
it('look for VS2022 by version number', async function () {
const finder = new TestVisualStudioFinder(semverV1, '2022')
finder.msBuildPathExists = (path) => {
return true
}
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,
'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')
})
'c:\\program files (x86)\\microsoft visual studio\\2019\\BUILDTOOLS')
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 () {
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.versionYear, 2022)
})
it('latest version should be found by default', async function () {
const finder = new TestVisualStudioFinder(semverV1, null)
finder.msBuildPathExists = (path) => {
return true
}
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'
// VSINSTALLDIR is not defined on Visual C++ Build Tools 2015
delete process.env.VSINSTALLDIR
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2015')
})
const finder = new TestVisualStudioFinder(semverV1, null)
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 =
'c:\\program files (x86)\\microsoft visual studio\\2019\\BUILDTOOLS\\VC'
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path,
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools')
})
const finder = new TestVisualStudioFinder(semverV1, null)
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 =
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildToolsUnusable\\VC'
const finder = new TestVisualStudioFinder(semverV1, null, (err, info) => {
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
})
const finder = new TestVisualStudioFinder(semverV1, null)
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'
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015',
(err, info) => {
assert.strictEqual(err, null)
assert.deepStrictEqual(info.path, 'C:\\VS2015')
})
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015')
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 =
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC'
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015',
(err, info) => {
assert.ok(/find .* Visual Studio/i.test(err), 'expect error')
assert.ok(!info, 'no data')
})
const finder = new TestVisualStudioFinder(semverV1, 'C:\\VS2015')
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'
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 path = require('path')
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 stream = require('stream')
const streamPipeline = util.promisify(stream.pipeline)
const { pipeline: streamPipeline } = require('stream/promises')
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 () {
it('EACCES retry once', async () => {
const fs = {
promises: {
stat (_) {
const err = new Error()
err.code = 'EACCES'
assert.ok(true)
throw err
let statCalled = 0
const mockInstall = createInstall({
'graceful-fs': {
promises: {
stat (_) {
const err = new Error()
err.code = 'EACCES'
statCalled++
throw err
}
}
}
}
})
const Gyp = {
devDir: __dirname,
opts: {
ensure: true
},
commands: {
install (argv, cb) {
install(fs, Gyp, argv).then(cb, cb)
},
remove (_, cb) {
cb()
}
install: (...args) => mockInstall.install(Gyp, ...args),
remove: async () => {}
}
}
let err
try {
await install(fs, Gyp, [])
} catch (err) {
await Gyp.commands.install([])
} 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)
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.satisfies(process.version, '<10')
async function parallelInstallsTest (test, fs, devDir, prog) {
async function parallelInstallsTest (test, devDir, prog) {
if (skipParallelInstallTests) {
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 Promise.all([
install(fs, prog, []),
install(fs, prog, []),
install(fs, prog, []),
install(fs, prog, []),
install(fs, prog, []),
install(fs, prog, []),
install(fs, prog, []),
install(fs, prog, []),
install(fs, prog, []),
install(fs, prog, [])
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, []),
install(prog, [])
])
}
it('parallel installs (ensure=true)', async function () {
this.timeout(600000)
const fs = require('graceful-fs')
const devDir = await util.promisify(fs.mkdtemp)(path.join(os.tmpdir(), 'node-gyp-test-'))
const devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-'))
const prog = gyp()
prog.parseArgv([])
prog.devDir = devDir
prog.opts.ensure = true
await parallelInstallsTest(this, fs, devDir, prog)
await parallelInstallsTest(this, devDir, prog)
})
it('parallel installs (ensure=false)', async function () {
this.timeout(600000)
const fs = require('graceful-fs')
const devDir = await util.promisify(fs.mkdtemp)(path.join(os.tmpdir(), 'node-gyp-test-'))
const devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-'))
const prog = gyp()
prog.parseArgv([])
prog.devDir = devDir
prog.opts.ensure = false
await parallelInstallsTest(this, fs, devDir, prog)
await parallelInstallsTest(this, devDir, prog)
})
it('parallel installs (tarball)', async function () {
this.timeout(600000)
const fs = require('graceful-fs')
const devDir = await util.promisify(fs.mkdtemp)(path.join(os.tmpdir(), 'node-gyp-test-'))
const devDir = await mkdtemp(path.join(os.tmpdir(), 'node-gyp-test-'))
const prog = gyp()
prog.parseArgv([])
@ -123,9 +124,9 @@ describe('install', function () {
await streamPipeline(
(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)
})
})