From 9ac2c276b05f91faa4354b66cb6389f0501f074e Mon Sep 17 00:00:00 2001 From: Leslie Chow Date: Sat, 22 Aug 2026 01:24:17 +0800 Subject: [PATCH 1/3] fix: print actionable hints when gyp fails on Android with missing NDK Re-trigger CI: previous failures were unrelated to this diff (ruff format flags README.md which this PR does not touch, and windows jobs failed on a flaky hello_world download). --- lib/configure.js | 45 +++++++++++++++++++++++- test/test-ndk-undefined-variable-hint.js | 38 ++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 test/test-ndk-undefined-variable-hint.js diff --git a/lib/configure.js b/lib/configure.js index ee672cfbf2..361244a37b 100644 --- a/lib/configure.js +++ b/lib/configure.js @@ -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 @@ -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' diff --git a/test/test-ndk-undefined-variable-hint.js b/test/test-ndk-undefined-variable-hint.js new file mode 100644 index 0000000000..c4f69e39d6 --- /dev/null +++ b/test/test-ndk-undefined-variable-hint.js @@ -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) + }) +}) From 41b42daad186644e306d515a1feddcb10277b190 Mon Sep 17 00:00:00 2001 From: Leslie Chow Date: Sat, 22 Aug 2026 01:25:51 +0800 Subject: [PATCH 2/3] ci: re-trigger workflow run Previous checks were stale: ruff format flags README.md (untouched by this PR) and windows jobs failed on a flaky hello_world download. --- test/test-ndk-undefined-variable-hint.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test-ndk-undefined-variable-hint.js b/test/test-ndk-undefined-variable-hint.js index c4f69e39d6..fb62c27a92 100644 --- a/test/test-ndk-undefined-variable-hint.js +++ b/test/test-ndk-undefined-variable-hint.js @@ -36,3 +36,5 @@ describe('isNdkUndefinedVariableHint', function () { assert.strictEqual(isHint(''), false) }) }) + +// re-trigger CI after stale failures (ruff README drift / flaky windows download) From dced82447e62e977351b7334c7269b2448502bbb Mon Sep 17 00:00:00 2001 From: Leslie Chow Date: Sat, 22 Aug 2026 02:08:20 +0800 Subject: [PATCH 3/3] ci: refresh PR checks --- test/test-ndk-undefined-variable-hint.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test-ndk-undefined-variable-hint.js b/test/test-ndk-undefined-variable-hint.js index fb62c27a92..1915fceed2 100644 --- a/test/test-ndk-undefined-variable-hint.js +++ b/test/test-ndk-undefined-variable-hint.js @@ -38,3 +38,4 @@ describe('isNdkUndefinedVariableHint', function () { }) // re-trigger CI after stale failures (ruff README drift / flaky windows download) +// ci: refresh PR checks