Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion lib/configure.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,37 @@ async function configure (gyp, argv) {
process.env.PYTHONPATH = pypath.join(win ? ';' : ':')

await new Promise((resolve, reject) => {
const cp = gyp.spawn(python, argv)
// Capture gyp's stderr (still forwarded to the terminal) so we can
// recognise known failure modes and print actionable hints.
const cp = gyp.spawn(python, argv, { stdio: ['inherit', 'inherit', 'pipe'] })
let gypStderr = ''
if (cp.stderr) {
cp.stderr.on('data', (chunk) => {
gypStderr += chunk
process.stderr.write(chunk)
})
}
cp.on('exit', (code) => {
if (code !== 0) {
// Only hint when the undefined variable comes from the Node/NDK
// header toolchain (android_ndk_* variables, or any variable while
// loading common.gypi), not when a binding.gyp itself references an
// undefined variable — that is a project bug and this hint would
// send the user down the wrong path.
const ndkHint = process.platform === 'android' &&
isNdkUndefinedVariableHint(gypStderr)
if (ndkHint) {
log.error('gyp', [
'The Node headers downloaded for this target reference NDK',
'variables, but no NDK is installed. On Android (e.g. Termux)',
'you usually want to compile against the system toolchain instead:',
'make sure node-gyp uses the locally installed headers',
'($PREFIX/include/node) rather than the official ones downloaded',
'from nodejs.org, e.g. by rebuilding Node with',
'--use-prefix-to-find-headers or by passing --nodedir pointing at',
'your local headers.'
].join('\n'))
}
reject(new Error('`gyp` failed with exit code: ' + code))
} else {
// we're done
Expand All @@ -324,5 +352,20 @@ async function configure (gyp, argv) {
}
}

/**
* Detect the "Undefined variable" gyp failure that comes from the Node/NDK
* header toolchain (android_ndk_* variables, or any undefined variable while
* loading common.gypi) rather than from the project's own binding.gyp.
* Exported for tests.
* @param {string} gypStderr - captured gyp stderr (also forwarded to the
* terminal byte-for-byte by the caller).
* @returns {boolean}
*/
function isNdkUndefinedVariableHint (gypStderr) {
return /Undefined variable android_ndk_/.test(gypStderr) ||
/Undefined variable [A-Za-z0-9_]+ in .*common\.gypi/.test(gypStderr)
}

module.exports = configure
module.exports.isNdkUndefinedVariableHint = isNdkUndefinedVariableHint
module.exports.usage = 'Generates ' + (win ? 'MSVC project files' : 'a Makefile') + ' for the current module'
38 changes: 38 additions & 0 deletions test/test-ndk-undefined-variable-hint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict'

const { describe, it } = require('mocha')
const assert = require('assert')
const configure = require('../lib/configure')

const isHint = configure.isNdkUndefinedVariableHint

describe('isNdkUndefinedVariableHint', function () {
it('detects the android_ndk_path failure from the official headers', function () {
const stderr = 'gyp: Undefined variable android_ndk_path in binding.gyp while trying to load binding.gyp\n'
assert.strictEqual(isHint(stderr), true)
})

it('detects other android_ndk_* variables', function () {
const stderr = 'gyp: Undefined variable android_ndk_include_dir in binding.gyp while trying to load binding.gyp\n'
assert.strictEqual(isHint(stderr), true)
})

it('detects an undefined variable while loading common.gypi', function () {
const stderr = 'gyp: Undefined variable some_var in /usr/include/node/common.gypi while trying to load binding.gyp\n'
assert.strictEqual(isHint(stderr), true)
})

it('ignores an undefined variable in the project binding.gyp', function () {
const stderr = 'gyp: Undefined variable my_custom_flag in binding.gyp while trying to load binding.gyp\n'
assert.strictEqual(isHint(stderr), false)
})

it('ignores unrelated gyp errors', function () {
const stderr = "gyp: 'x' doesn't look like a valid filename\n"
assert.strictEqual(isHint(stderr), false)
})

it('ignores empty stderr', function () {
assert.strictEqual(isHint(''), false)
})
})
Loading