Skip to content

Commit

Permalink
Add lots of findPython() tests.
Browse files Browse the repository at this point in the history
Break up findPython() into pieces that can be tested in isolation and
add tests that do so.

PR-URL: #992
Reviewed-By: Johan Bergström <[email protected]>
  • Loading branch information
bnoordhuis committed Oct 8, 2016
1 parent afc766a commit e3778d9
Show file tree
Hide file tree
Showing 2 changed files with 345 additions and 80 deletions.
187 changes: 108 additions & 79 deletions lib/configure.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module.exports = exports = configure
module.exports.test = { findAccessibleSync: findAccessibleSync,
findPython: findPython }
module.exports.test = {
PythonFinder: PythonFinder,
findAccessibleSync: findAccessibleSync,
findPython: findPython,
}

/**
* Module dependencies.
Expand All @@ -16,8 +19,6 @@ var fs = require('graceful-fs')
, cp = require('child_process')
, extend = require('util')._extend
, processRelease = require('./process-release')
, spawn = cp.spawn
, execFile = cp.execFile
, win = process.platform == 'win32'
, findNodeDirectory = require('./find-node-directory')
, msgFormat = require('util').format
Expand Down Expand Up @@ -336,34 +337,45 @@ function findAccessibleSync (logprefix, dir, candidates) {
return undefined
}

function findPython (python, callback) {
checkPython()
function PythonFinder(python, callback) {
this.callback = callback
this.python = python
}

// Check if Python is in the $PATH
function checkPython () {
log.verbose('check python', 'checking for Python executable "%s" in the PATH', python)
which(python, function (err, execPath) {
PythonFinder.prototype = {
env: process.env,
execFile: cp.execFile,
log: log,
stat: fs.stat,
which: which,
win: win,

checkPython: function checkPython () {
this.log.verbose('check python',
'checking for Python executable "%s" in the PATH',
this.python)
this.which(this.python, function (err, execPath) {
if (err) {
log.verbose('`which` failed', python, err)
if (python === 'python2') {
python = 'python'
return checkPython()
this.log.verbose('`which` failed', this.python, err)
if (this.python === 'python2') {
this.python = 'python'
return this.checkPython()
}
if (win) {
checkPythonLauncher()
if (this.win) {
this.checkPythonLauncher()
} else {
failNoPython()
this.failNoPython()
}
} else {
log.verbose('`which` succeeded', python, execPath)
// Found the `python` exceutable, and from now on we use it explicitly.
this.log.verbose('`which` succeeded', this.python, execPath)
// Found the `python` executable, and from now on we use it explicitly.
// This solves #667 and #750 (`execFile` won't run batch files
// (*.cmd, and *.bat))
python = execPath
checkPythonVersion()
this.python = execPath
this.checkPythonVersion()
}
})
}
}.bind(this))
},

// Distributions of Python on Windows by default install with the "py.exe"
// Python launcher which is more likely to exist than the Python executable
Expand All @@ -373,88 +385,105 @@ function findPython (python, callback) {
// the first command line argument. Since "py.exe -2" would be an invalid
// executable for "execFile", we have to use the launcher to figure out
// where the actual "python.exe" executable is located.
function checkPythonLauncher () {
log.verbose('could not find "' + python + '". checking python launcher')
var env = extend({}, process.env)
checkPythonLauncher: function checkPythonLauncher () {
this.log.verbose(
'could not find "' + this.python + '". checking python launcher')
var env = extend({}, this.env)
env.TERM = 'dumb'

var launcherArgs = ['-2', '-c', 'import sys; print sys.executable']
execFile('py.exe', launcherArgs, { env: env }, function (err, stdout) {
this.execFile('py.exe', launcherArgs, { env: env }, function (err, stdout) {
if (err) {
guessPython()
return
}
python = stdout.trim()
log.verbose('check python launcher', 'python executable found: %j', python)
checkPythonVersion()
})
}

// Called on Windows when "python" isn't available in the current $PATH.
// We're gonna check if "%SystemDrive%\python27\python.exe" exists.
function guessPython () {
log.verbose('could not find "' + python + '". guessing location')
var rootDir = process.env.SystemDrive || 'C:\\'
if (rootDir[rootDir.length - 1] !== '\\') {
rootDir += '\\'
}
var pythonPath = path.resolve(rootDir, 'Python27', 'python.exe')
log.verbose('ensuring that file exists:', pythonPath)
fs.stat(pythonPath, function (err, stat) {
if (err) {
if (err.code == 'ENOENT') {
failNoPython()
} else {
callback(err)
}
this.guessPython()
return
}
python = pythonPath
checkPythonVersion()
})
}

function checkPythonVersion () {
var env = extend({}, process.env)
this.python = stdout.trim()
this.log.verbose('check python launcher',
'python executable found: %j',
this.python)
this.checkPythonVersion()
}.bind(this))
},

checkPythonVersion: function checkPythonVersion () {
var args = ['-c', 'import platform; print(platform.python_version());']
var env = extend({}, this.env)
env.TERM = 'dumb'

execFile(python, ['-c', 'import platform; print(platform.python_version());'], { env: env }, function (err, stdout) {
this.execFile(this.python, args, { env: env }, function (err, stdout) {
if (err) {
return callback(err)
return this.callback(err)
}
log.verbose('check python version', '`%s -c "import platform; print(platform.python_version());"` returned: %j', python, stdout)
this.log.verbose('check python version',
'`%s -c "' + args[1] + '"` returned: %j',
this.python, stdout)
var version = stdout.trim()
if (~version.indexOf('+')) {
log.silly('stripping "+" sign(s) from version')
this.log.silly('stripping "+" sign(s) from version')
version = version.replace(/\+/g, '')
}
if (~version.indexOf('rc')) {
log.silly('stripping "rc" identifier from version')
this.log.silly('stripping "rc" identifier from version')
version = version.replace(/rc(.*)$/ig, '')
}
var range = semver.Range('>=2.5.0 <3.0.0')
var valid = false
try {
valid = range.test(version)
} catch (e) {
log.silly('range.test() error', e)
this.log.silly('range.test() error', e)
}
if (valid) {
callback(null, python)
this.callback(null, this.python)
} else {
failPythonVersion(version)
this.failPythonVersion(version)
}
})
}
}.bind(this))
},

failNoPython: function failNoPython () {
var errmsg =
'Can\'t find Python executable "' + this.python +
'", you can set the PYTHON env variable.'
this.callback(new Error(errmsg))
},

failPythonVersion: function failPythonVersion (badVersion) {
var errmsg =
'Python executable "' + this.python +
'" is v' + badVersion + ', which is not supported by gyp.\n' +
'You can pass the --python switch to point to ' +
'Python >= v2.5.0 & < 3.0.0.'
this.callback(new Error(errmsg))
},

function failNoPython () {
callback(new Error('Can\'t find Python executable "' + python +
'", you can set the PYTHON env variable.'))
}
// Called on Windows when "python" isn't available in the current $PATH.
// We are going to check if "%SystemDrive%\python27\python.exe" exists.
guessPython: function guessPython () {
this.log.verbose('could not find "' + this.python + '". guessing location')
var rootDir = this.env.SystemDrive || 'C:\\'
if (rootDir[rootDir.length - 1] !== '\\') {
rootDir += '\\'
}
var resolve = path.win32 && path.win32.resolve || path.resolve
var pythonPath = resolve(rootDir, 'Python27', 'python.exe')
this.log.verbose('ensuring that file exists:', pythonPath)
this.stat(pythonPath, function (err, stat) {
if (err) {
if (err.code == 'ENOENT') {
this.failNoPython()
} else {
this.callback(err)
}
return
}
this.python = pythonPath
this.checkPythonVersion()
}.bind(this))
},
}

function failPythonVersion (badVersion) {
callback(new Error('Python executable "' + python +
'" is v' + badVersion + ', which is not supported by gyp.\n' +
'You can pass the --python switch to point to Python >= v2.5.0 & < 3.0.0.'))
}
function findPython (python, callback) {
var finder = new PythonFinder(python, callback)
finder.checkPython()
}
Loading

0 comments on commit e3778d9

Please sign in to comment.