11cb0ef41Sopenharmony_ci'use strict' 21cb0ef41Sopenharmony_ci 31cb0ef41Sopenharmony_ciconst log = require('./log') 41cb0ef41Sopenharmony_ciconst semver = require('semver') 51cb0ef41Sopenharmony_ciconst { execFile } = require('./util') 61cb0ef41Sopenharmony_ciconst win = process.platform === 'win32' 71cb0ef41Sopenharmony_ci 81cb0ef41Sopenharmony_cifunction getOsUserInfo () { 91cb0ef41Sopenharmony_ci try { 101cb0ef41Sopenharmony_ci return require('os').userInfo().username 111cb0ef41Sopenharmony_ci } catch {} 121cb0ef41Sopenharmony_ci} 131cb0ef41Sopenharmony_ci 141cb0ef41Sopenharmony_ciconst systemDrive = process.env.SystemDrive || 'C:' 151cb0ef41Sopenharmony_ciconst username = process.env.USERNAME || process.env.USER || getOsUserInfo() 161cb0ef41Sopenharmony_ciconst localAppData = process.env.LOCALAPPDATA || `${systemDrive}\\${username}\\AppData\\Local` 171cb0ef41Sopenharmony_ciconst foundLocalAppData = process.env.LOCALAPPDATA || username 181cb0ef41Sopenharmony_ciconst programFiles = process.env.ProgramW6432 || process.env.ProgramFiles || `${systemDrive}\\Program Files` 191cb0ef41Sopenharmony_ciconst programFilesX86 = process.env['ProgramFiles(x86)'] || `${programFiles} (x86)` 201cb0ef41Sopenharmony_ci 211cb0ef41Sopenharmony_ciconst winDefaultLocationsArray = [] 221cb0ef41Sopenharmony_cifor (const majorMinor of ['311', '310', '39', '38']) { 231cb0ef41Sopenharmony_ci if (foundLocalAppData) { 241cb0ef41Sopenharmony_ci winDefaultLocationsArray.push( 251cb0ef41Sopenharmony_ci `${localAppData}\\Programs\\Python\\Python${majorMinor}\\python.exe`, 261cb0ef41Sopenharmony_ci `${programFiles}\\Python${majorMinor}\\python.exe`, 271cb0ef41Sopenharmony_ci `${localAppData}\\Programs\\Python\\Python${majorMinor}-32\\python.exe`, 281cb0ef41Sopenharmony_ci `${programFiles}\\Python${majorMinor}-32\\python.exe`, 291cb0ef41Sopenharmony_ci `${programFilesX86}\\Python${majorMinor}-32\\python.exe` 301cb0ef41Sopenharmony_ci ) 311cb0ef41Sopenharmony_ci } else { 321cb0ef41Sopenharmony_ci winDefaultLocationsArray.push( 331cb0ef41Sopenharmony_ci `${programFiles}\\Python${majorMinor}\\python.exe`, 341cb0ef41Sopenharmony_ci `${programFiles}\\Python${majorMinor}-32\\python.exe`, 351cb0ef41Sopenharmony_ci `${programFilesX86}\\Python${majorMinor}-32\\python.exe` 361cb0ef41Sopenharmony_ci ) 371cb0ef41Sopenharmony_ci } 381cb0ef41Sopenharmony_ci} 391cb0ef41Sopenharmony_ci 401cb0ef41Sopenharmony_ciclass PythonFinder { 411cb0ef41Sopenharmony_ci static findPython = (...args) => new PythonFinder(...args).findPython() 421cb0ef41Sopenharmony_ci 431cb0ef41Sopenharmony_ci log = log.withPrefix('find Python') 441cb0ef41Sopenharmony_ci argsExecutable = ['-c', 'import sys; print(sys.executable);'] 451cb0ef41Sopenharmony_ci argsVersion = ['-c', 'import sys; print("%s.%s.%s" % sys.version_info[:3]);'] 461cb0ef41Sopenharmony_ci semverRange = '>=3.6.0' 471cb0ef41Sopenharmony_ci 481cb0ef41Sopenharmony_ci // These can be overridden for testing: 491cb0ef41Sopenharmony_ci execFile = execFile 501cb0ef41Sopenharmony_ci env = process.env 511cb0ef41Sopenharmony_ci win = win 521cb0ef41Sopenharmony_ci pyLauncher = 'py.exe' 531cb0ef41Sopenharmony_ci winDefaultLocations = winDefaultLocationsArray 541cb0ef41Sopenharmony_ci 551cb0ef41Sopenharmony_ci constructor (configPython) { 561cb0ef41Sopenharmony_ci this.configPython = configPython 571cb0ef41Sopenharmony_ci this.errorLog = [] 581cb0ef41Sopenharmony_ci } 591cb0ef41Sopenharmony_ci 601cb0ef41Sopenharmony_ci // Logs a message at verbose level, but also saves it to be displayed later 611cb0ef41Sopenharmony_ci // at error level if an error occurs. This should help diagnose the problem. 621cb0ef41Sopenharmony_ci addLog (message) { 631cb0ef41Sopenharmony_ci this.log.verbose(message) 641cb0ef41Sopenharmony_ci this.errorLog.push(message) 651cb0ef41Sopenharmony_ci } 661cb0ef41Sopenharmony_ci 671cb0ef41Sopenharmony_ci // Find Python by trying a sequence of possibilities. 681cb0ef41Sopenharmony_ci // Ignore errors, keep trying until Python is found. 691cb0ef41Sopenharmony_ci async findPython () { 701cb0ef41Sopenharmony_ci const SKIP = 0 711cb0ef41Sopenharmony_ci const FAIL = 1 721cb0ef41Sopenharmony_ci const toCheck = (() => { 731cb0ef41Sopenharmony_ci if (this.env.NODE_GYP_FORCE_PYTHON) { 741cb0ef41Sopenharmony_ci return [{ 751cb0ef41Sopenharmony_ci before: () => { 761cb0ef41Sopenharmony_ci this.addLog( 771cb0ef41Sopenharmony_ci 'checking Python explicitly set from NODE_GYP_FORCE_PYTHON') 781cb0ef41Sopenharmony_ci this.addLog('- process.env.NODE_GYP_FORCE_PYTHON is ' + 791cb0ef41Sopenharmony_ci `"${this.env.NODE_GYP_FORCE_PYTHON}"`) 801cb0ef41Sopenharmony_ci }, 811cb0ef41Sopenharmony_ci check: () => this.checkCommand(this.env.NODE_GYP_FORCE_PYTHON) 821cb0ef41Sopenharmony_ci }] 831cb0ef41Sopenharmony_ci } 841cb0ef41Sopenharmony_ci 851cb0ef41Sopenharmony_ci const checks = [ 861cb0ef41Sopenharmony_ci { 871cb0ef41Sopenharmony_ci before: () => { 881cb0ef41Sopenharmony_ci if (!this.configPython) { 891cb0ef41Sopenharmony_ci this.addLog( 901cb0ef41Sopenharmony_ci 'Python is not set from command line or npm configuration') 911cb0ef41Sopenharmony_ci return SKIP 921cb0ef41Sopenharmony_ci } 931cb0ef41Sopenharmony_ci this.addLog('checking Python explicitly set from command line or ' + 941cb0ef41Sopenharmony_ci 'npm configuration') 951cb0ef41Sopenharmony_ci this.addLog('- "--python=" or "npm config get python" is ' + 961cb0ef41Sopenharmony_ci `"${this.configPython}"`) 971cb0ef41Sopenharmony_ci }, 981cb0ef41Sopenharmony_ci check: () => this.checkCommand(this.configPython) 991cb0ef41Sopenharmony_ci }, 1001cb0ef41Sopenharmony_ci { 1011cb0ef41Sopenharmony_ci before: () => { 1021cb0ef41Sopenharmony_ci if (!this.env.PYTHON) { 1031cb0ef41Sopenharmony_ci this.addLog('Python is not set from environment variable ' + 1041cb0ef41Sopenharmony_ci 'PYTHON') 1051cb0ef41Sopenharmony_ci return SKIP 1061cb0ef41Sopenharmony_ci } 1071cb0ef41Sopenharmony_ci this.addLog('checking Python explicitly set from environment ' + 1081cb0ef41Sopenharmony_ci 'variable PYTHON') 1091cb0ef41Sopenharmony_ci this.addLog(`- process.env.PYTHON is "${this.env.PYTHON}"`) 1101cb0ef41Sopenharmony_ci }, 1111cb0ef41Sopenharmony_ci check: () => this.checkCommand(this.env.PYTHON) 1121cb0ef41Sopenharmony_ci } 1131cb0ef41Sopenharmony_ci ] 1141cb0ef41Sopenharmony_ci 1151cb0ef41Sopenharmony_ci if (this.win) { 1161cb0ef41Sopenharmony_ci checks.push({ 1171cb0ef41Sopenharmony_ci before: () => { 1181cb0ef41Sopenharmony_ci this.addLog( 1191cb0ef41Sopenharmony_ci 'checking if the py launcher can be used to find Python 3') 1201cb0ef41Sopenharmony_ci }, 1211cb0ef41Sopenharmony_ci check: () => this.checkPyLauncher() 1221cb0ef41Sopenharmony_ci }) 1231cb0ef41Sopenharmony_ci } 1241cb0ef41Sopenharmony_ci 1251cb0ef41Sopenharmony_ci checks.push(...[ 1261cb0ef41Sopenharmony_ci { 1271cb0ef41Sopenharmony_ci before: () => { this.addLog('checking if "python3" can be used') }, 1281cb0ef41Sopenharmony_ci check: () => this.checkCommand('python3') 1291cb0ef41Sopenharmony_ci }, 1301cb0ef41Sopenharmony_ci { 1311cb0ef41Sopenharmony_ci before: () => { this.addLog('checking if "python" can be used') }, 1321cb0ef41Sopenharmony_ci check: () => this.checkCommand('python') 1331cb0ef41Sopenharmony_ci } 1341cb0ef41Sopenharmony_ci ]) 1351cb0ef41Sopenharmony_ci 1361cb0ef41Sopenharmony_ci if (this.win) { 1371cb0ef41Sopenharmony_ci for (let i = 0; i < this.winDefaultLocations.length; ++i) { 1381cb0ef41Sopenharmony_ci const location = this.winDefaultLocations[i] 1391cb0ef41Sopenharmony_ci checks.push({ 1401cb0ef41Sopenharmony_ci before: () => this.addLog(`checking if Python is ${location}`), 1411cb0ef41Sopenharmony_ci check: () => this.checkExecPath(location) 1421cb0ef41Sopenharmony_ci }) 1431cb0ef41Sopenharmony_ci } 1441cb0ef41Sopenharmony_ci } 1451cb0ef41Sopenharmony_ci 1461cb0ef41Sopenharmony_ci return checks 1471cb0ef41Sopenharmony_ci })() 1481cb0ef41Sopenharmony_ci 1491cb0ef41Sopenharmony_ci for (const check of toCheck) { 1501cb0ef41Sopenharmony_ci const before = check.before() 1511cb0ef41Sopenharmony_ci if (before === SKIP) { 1521cb0ef41Sopenharmony_ci continue 1531cb0ef41Sopenharmony_ci } 1541cb0ef41Sopenharmony_ci if (before === FAIL) { 1551cb0ef41Sopenharmony_ci return this.fail() 1561cb0ef41Sopenharmony_ci } 1571cb0ef41Sopenharmony_ci try { 1581cb0ef41Sopenharmony_ci return await check.check() 1591cb0ef41Sopenharmony_ci } catch (err) { 1601cb0ef41Sopenharmony_ci this.log.silly('runChecks: err = %j', (err && err.stack) || err) 1611cb0ef41Sopenharmony_ci } 1621cb0ef41Sopenharmony_ci } 1631cb0ef41Sopenharmony_ci 1641cb0ef41Sopenharmony_ci return this.fail() 1651cb0ef41Sopenharmony_ci } 1661cb0ef41Sopenharmony_ci 1671cb0ef41Sopenharmony_ci // Check if command is a valid Python to use. 1681cb0ef41Sopenharmony_ci // Will exit the Python finder on success. 1691cb0ef41Sopenharmony_ci // If on Windows, run in a CMD shell to support BAT/CMD launchers. 1701cb0ef41Sopenharmony_ci async checkCommand (command) { 1711cb0ef41Sopenharmony_ci let exec = command 1721cb0ef41Sopenharmony_ci let args = this.argsExecutable 1731cb0ef41Sopenharmony_ci let shell = false 1741cb0ef41Sopenharmony_ci if (this.win) { 1751cb0ef41Sopenharmony_ci // Arguments have to be manually quoted 1761cb0ef41Sopenharmony_ci exec = `"${exec}"` 1771cb0ef41Sopenharmony_ci args = args.map(a => `"${a}"`) 1781cb0ef41Sopenharmony_ci shell = true 1791cb0ef41Sopenharmony_ci } 1801cb0ef41Sopenharmony_ci 1811cb0ef41Sopenharmony_ci this.log.verbose(`- executing "${command}" to get executable path`) 1821cb0ef41Sopenharmony_ci // Possible outcomes: 1831cb0ef41Sopenharmony_ci // - Error: not in PATH, not executable or execution fails 1841cb0ef41Sopenharmony_ci // - Gibberish: the next command to check version will fail 1851cb0ef41Sopenharmony_ci // - Absolute path to executable 1861cb0ef41Sopenharmony_ci try { 1871cb0ef41Sopenharmony_ci const execPath = await this.run(exec, args, shell) 1881cb0ef41Sopenharmony_ci this.addLog(`- executable path is "${execPath}"`) 1891cb0ef41Sopenharmony_ci return this.checkExecPath(execPath) 1901cb0ef41Sopenharmony_ci } catch (err) { 1911cb0ef41Sopenharmony_ci this.addLog(`- "${command}" is not in PATH or produced an error`) 1921cb0ef41Sopenharmony_ci throw err 1931cb0ef41Sopenharmony_ci } 1941cb0ef41Sopenharmony_ci } 1951cb0ef41Sopenharmony_ci 1961cb0ef41Sopenharmony_ci // Check if the py launcher can find a valid Python to use. 1971cb0ef41Sopenharmony_ci // Will exit the Python finder on success. 1981cb0ef41Sopenharmony_ci // Distributions of Python on Windows by default install with the "py.exe" 1991cb0ef41Sopenharmony_ci // Python launcher which is more likely to exist than the Python executable 2001cb0ef41Sopenharmony_ci // being in the $PATH. 2011cb0ef41Sopenharmony_ci // Because the Python launcher supports Python 2 and Python 3, we should 2021cb0ef41Sopenharmony_ci // explicitly request a Python 3 version. This is done by supplying "-3" as 2031cb0ef41Sopenharmony_ci // the first command line argument. Since "py.exe -3" would be an invalid 2041cb0ef41Sopenharmony_ci // executable for "execFile", we have to use the launcher to figure out 2051cb0ef41Sopenharmony_ci // where the actual "python.exe" executable is located. 2061cb0ef41Sopenharmony_ci async checkPyLauncher () { 2071cb0ef41Sopenharmony_ci this.log.verbose(`- executing "${this.pyLauncher}" to get Python 3 executable path`) 2081cb0ef41Sopenharmony_ci // Possible outcomes: same as checkCommand 2091cb0ef41Sopenharmony_ci try { 2101cb0ef41Sopenharmony_ci const execPath = await this.run(this.pyLauncher, ['-3', ...this.argsExecutable], false) 2111cb0ef41Sopenharmony_ci this.addLog(`- executable path is "${execPath}"`) 2121cb0ef41Sopenharmony_ci return this.checkExecPath(execPath) 2131cb0ef41Sopenharmony_ci } catch (err) { 2141cb0ef41Sopenharmony_ci this.addLog(`- "${this.pyLauncher}" is not in PATH or produced an error`) 2151cb0ef41Sopenharmony_ci throw err 2161cb0ef41Sopenharmony_ci } 2171cb0ef41Sopenharmony_ci } 2181cb0ef41Sopenharmony_ci 2191cb0ef41Sopenharmony_ci // Check if a Python executable is the correct version to use. 2201cb0ef41Sopenharmony_ci // Will exit the Python finder on success. 2211cb0ef41Sopenharmony_ci async checkExecPath (execPath) { 2221cb0ef41Sopenharmony_ci this.log.verbose(`- executing "${execPath}" to get version`) 2231cb0ef41Sopenharmony_ci // Possible outcomes: 2241cb0ef41Sopenharmony_ci // - Error: executable can not be run (likely meaning the command wasn't 2251cb0ef41Sopenharmony_ci // a Python executable and the previous command produced gibberish) 2261cb0ef41Sopenharmony_ci // - Gibberish: somehow the last command produced an executable path, 2271cb0ef41Sopenharmony_ci // this will fail when verifying the version 2281cb0ef41Sopenharmony_ci // - Version of the Python executable 2291cb0ef41Sopenharmony_ci try { 2301cb0ef41Sopenharmony_ci const version = await this.run(execPath, this.argsVersion, false) 2311cb0ef41Sopenharmony_ci this.addLog(`- version is "${version}"`) 2321cb0ef41Sopenharmony_ci 2331cb0ef41Sopenharmony_ci const range = new semver.Range(this.semverRange) 2341cb0ef41Sopenharmony_ci let valid = false 2351cb0ef41Sopenharmony_ci try { 2361cb0ef41Sopenharmony_ci valid = range.test(version) 2371cb0ef41Sopenharmony_ci } catch (err) { 2381cb0ef41Sopenharmony_ci this.log.silly('range.test() threw:\n%s', err.stack) 2391cb0ef41Sopenharmony_ci this.addLog(`- "${execPath}" does not have a valid version`) 2401cb0ef41Sopenharmony_ci this.addLog('- is it a Python executable?') 2411cb0ef41Sopenharmony_ci throw err 2421cb0ef41Sopenharmony_ci } 2431cb0ef41Sopenharmony_ci if (!valid) { 2441cb0ef41Sopenharmony_ci this.addLog(`- version is ${version} - should be ${this.semverRange}`) 2451cb0ef41Sopenharmony_ci this.addLog('- THIS VERSION OF PYTHON IS NOT SUPPORTED') 2461cb0ef41Sopenharmony_ci throw new Error(`Found unsupported Python version ${version}`) 2471cb0ef41Sopenharmony_ci } 2481cb0ef41Sopenharmony_ci return this.succeed(execPath, version) 2491cb0ef41Sopenharmony_ci } catch (err) { 2501cb0ef41Sopenharmony_ci this.addLog(`- "${execPath}" could not be run`) 2511cb0ef41Sopenharmony_ci throw err 2521cb0ef41Sopenharmony_ci } 2531cb0ef41Sopenharmony_ci } 2541cb0ef41Sopenharmony_ci 2551cb0ef41Sopenharmony_ci // Run an executable or shell command, trimming the output. 2561cb0ef41Sopenharmony_ci async run (exec, args, shell) { 2571cb0ef41Sopenharmony_ci const env = Object.assign({}, this.env) 2581cb0ef41Sopenharmony_ci env.TERM = 'dumb' 2591cb0ef41Sopenharmony_ci const opts = { env, shell } 2601cb0ef41Sopenharmony_ci 2611cb0ef41Sopenharmony_ci this.log.silly('execFile: exec = %j', exec) 2621cb0ef41Sopenharmony_ci this.log.silly('execFile: args = %j', args) 2631cb0ef41Sopenharmony_ci this.log.silly('execFile: opts = %j', opts) 2641cb0ef41Sopenharmony_ci try { 2651cb0ef41Sopenharmony_ci const [err, stdout, stderr] = await this.execFile(exec, args, opts) 2661cb0ef41Sopenharmony_ci this.log.silly('execFile result: err = %j', (err && err.stack) || err) 2671cb0ef41Sopenharmony_ci this.log.silly('execFile result: stdout = %j', stdout) 2681cb0ef41Sopenharmony_ci this.log.silly('execFile result: stderr = %j', stderr) 2691cb0ef41Sopenharmony_ci return stdout.trim() 2701cb0ef41Sopenharmony_ci } catch (err) { 2711cb0ef41Sopenharmony_ci this.log.silly('execFile: threw:\n%s', err.stack) 2721cb0ef41Sopenharmony_ci throw err 2731cb0ef41Sopenharmony_ci } 2741cb0ef41Sopenharmony_ci } 2751cb0ef41Sopenharmony_ci 2761cb0ef41Sopenharmony_ci succeed (execPath, version) { 2771cb0ef41Sopenharmony_ci this.log.info(`using Python version ${version} found at "${execPath}"`) 2781cb0ef41Sopenharmony_ci return execPath 2791cb0ef41Sopenharmony_ci } 2801cb0ef41Sopenharmony_ci 2811cb0ef41Sopenharmony_ci fail () { 2821cb0ef41Sopenharmony_ci const errorLog = this.errorLog.join('\n') 2831cb0ef41Sopenharmony_ci 2841cb0ef41Sopenharmony_ci const pathExample = this.win 2851cb0ef41Sopenharmony_ci ? 'C:\\Path\\To\\python.exe' 2861cb0ef41Sopenharmony_ci : '/path/to/pythonexecutable' 2871cb0ef41Sopenharmony_ci // For Windows 80 col console, use up to the column before the one marked 2881cb0ef41Sopenharmony_ci // with X (total 79 chars including logger prefix, 58 chars usable here): 2891cb0ef41Sopenharmony_ci // X 2901cb0ef41Sopenharmony_ci const info = [ 2911cb0ef41Sopenharmony_ci '**********************************************************', 2921cb0ef41Sopenharmony_ci 'You need to install the latest version of Python.', 2931cb0ef41Sopenharmony_ci 'Node-gyp should be able to find and use Python. If not,', 2941cb0ef41Sopenharmony_ci 'you can try one of the following options:', 2951cb0ef41Sopenharmony_ci `- Use the switch --python="${pathExample}"`, 2961cb0ef41Sopenharmony_ci ' (accepted by both node-gyp and npm)', 2971cb0ef41Sopenharmony_ci '- Set the environment variable PYTHON', 2981cb0ef41Sopenharmony_ci '- Set the npm configuration variable python:', 2991cb0ef41Sopenharmony_ci ` npm config set python "${pathExample}"`, 3001cb0ef41Sopenharmony_ci 'For more information consult the documentation at:', 3011cb0ef41Sopenharmony_ci 'https://github.com/nodejs/node-gyp#installation', 3021cb0ef41Sopenharmony_ci '**********************************************************' 3031cb0ef41Sopenharmony_ci ].join('\n') 3041cb0ef41Sopenharmony_ci 3051cb0ef41Sopenharmony_ci this.log.error(`\n${errorLog}\n\n${info}\n`) 3061cb0ef41Sopenharmony_ci throw new Error('Could not find any Python installation to use') 3071cb0ef41Sopenharmony_ci } 3081cb0ef41Sopenharmony_ci} 3091cb0ef41Sopenharmony_ci 3101cb0ef41Sopenharmony_cimodule.exports = PythonFinder 311