From 329873141f0d3e3787d3c006801431da04e4ed0c Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Mon, 29 Jan 2024 14:03:30 -0500 Subject: [PATCH] src: add support for locally installed headers (#2964) Some linux distros allow headers to be installed through tools like rpm. If the runtime sets process.config.variables.use_prefix_to_find_headers, look for matching headers based on the directory set for the prefix in process.config.variables.prefix Signed-off-by: Michael Dawson Co-authored-by: Luke Karrys --- lib/configure.js | 28 +++++++- test/test-configure-nodedir.js | 123 +++++++++++++++++++++++++++++++++ test/test-configure-python.js | 1 + 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 test/test-configure-nodedir.js diff --git a/lib/configure.js b/lib/configure.js index 8da41a849d..e4b8c94e3d 100644 --- a/lib/configure.js +++ b/lib/configure.js @@ -1,6 +1,6 @@ 'use strict' -const { promises: fs } = require('graceful-fs') +const { promises: fs, readFileSync } = require('graceful-fs') const path = require('path') const log = require('./log') const os = require('os') @@ -13,6 +13,10 @@ const { findAccessibleSync } = require('./util') const { findPython } = require('./find-python') const { findVisualStudio } = win ? require('./find-visualstudio') : {} +const majorRe = /^#define NODE_MAJOR_VERSION (\d+)/m +const minorRe = /^#define NODE_MINOR_VERSION (\d+)/m +const patchRe = /^#define NODE_PATCH_VERSION (\d+)/m + async function configure (gyp, argv) { const buildDir = path.resolve('build') const configNames = ['config.gypi', 'common.gypi'] @@ -27,6 +31,28 @@ async function configure (gyp, argv) { // 'python' should be set by now process.env.PYTHON = python + if (!gyp.opts.nodedir && + process.config.variables.use_prefix_to_find_headers) { + // check if the headers can be found using the prefix specified + // at build time. Use them if they match the version expected + const prefix = process.config.variables.node_prefix + let availVersion + try { + const nodeVersionH = readFileSync(path.join(prefix, + 'include', 'node', 'node_version.h'), { encoding: 'utf8' }) + const major = nodeVersionH.match(majorRe)[1] + const minor = nodeVersionH.match(minorRe)[1] + const patch = nodeVersionH.match(patchRe)[1] + availVersion = major + '.' + minor + '.' + patch + } catch {} + if (availVersion === release.version) { + // ok version matches, use the headers + gyp.opts.nodedir = prefix + log.verbose('using local node headers based on prefix', + 'setting nodedir to ' + gyp.opts.nodedir) + } + } + if (gyp.opts.nodedir) { // --nodedir was specified. use that for the dev files nodeDir = gyp.opts.nodedir.replace(/^~/, os.homedir()) diff --git a/test/test-configure-nodedir.js b/test/test-configure-nodedir.js new file mode 100644 index 0000000000..a6debded06 --- /dev/null +++ b/test/test-configure-nodedir.js @@ -0,0 +1,123 @@ +'use strict' + +const { describe, it } = require('mocha') +const assert = require('assert') +const path = require('path') +const os = require('os') +const gyp = require('../lib/node-gyp') +const requireInject = require('require-inject') +const semver = require('semver') + +const versionSemver = semver.parse(process.version) + +const configure = requireInject('../lib/configure', { + 'graceful-fs': { + openSync: () => 0, + closeSync: () => {}, + existsSync: () => true, + readFileSync: () => '#define NODE_MAJOR_VERSION ' + versionSemver.major + '\n' + + '#define NODE_MINOR_VERSION ' + versionSemver.minor + '\n' + + '#define NODE_PATCH_VERSION ' + versionSemver.patch + '\n', + promises: { + stat: async () => ({}), + mkdir: async () => {}, + writeFile: async () => {} + } + } +}) + +const configure2 = requireInject('../lib/configure', { + 'graceful-fs': { + openSync: () => 0, + closeSync: () => {}, + existsSync: () => true, + readFileSync: () => '#define NODE_MAJOR_VERSION 8\n' + + '#define NODE_MINOR_VERSION 0\n' + + '#define NODE_PATCH_VERSION 0\n', + promises: { + stat: async () => ({}), + mkdir: async () => {}, + writeFile: async () => {} + } + } +}) + +const SPAWN_RESULT = cb => ({ on: function () { cb() } }) + +const driveLetter = os.platform() === 'win32' ? `${process.cwd().split(path.sep)[0]}` : '' +function checkTargetPath (target, value) { + let targetPath = path.join(path.sep, target, 'include', + 'node', 'common.gypi') + if (process.platform === 'win32') { + targetPath = driveLetter + targetPath + } + + return targetPath.localeCompare(value) === 0 +} + +describe('configure-nodedir', function () { + it('configure nodedir with node-gyp command line', function (done) { + const prog = gyp() + prog.parseArgv(['dummy_prog', 'dummy_script', '--nodedir=' + path.sep + 'usr']) + + prog.spawn = function (program, args) { + for (let i = 0; i < args.length; i++) { + if (checkTargetPath('usr', args[i])) { + return SPAWN_RESULT(done) + } + }; + assert.fail() + } + configure(prog, [], assert.fail) + }) + + if (process.config.variables.use_prefix_to_find_headers) { + it('use-prefix-to-find-headers build time option - match', function (done) { + const prog = gyp() + prog.parseArgv(['dummy_prog', 'dummy_script']) + + prog.spawn = function (program, args) { + for (let i = 0; i < args.length; i++) { + const nodedir = process.config.variables.node_prefix + if (checkTargetPath(nodedir, args[i])) { + return SPAWN_RESULT(done) + } + }; + assert.fail() + } + configure(prog, [], assert.fail) + }) + + it('use-prefix-to-find-headers build time option - no match', function (done) { + const prog = gyp() + prog.parseArgv(['dummy_prog', 'dummy_script']) + + prog.spawn = function (program, args) { + for (let i = 0; i < args.length; i++) { + const nodedir = process.config.variables.node_prefix + if (checkTargetPath(nodedir, args[i])) { + assert.fail() + } + }; + return SPAWN_RESULT(done) + } + configure2(prog, [], assert.fail) + }) + + it('use-prefix-to-find-headers build time option, target specified', function (done) { + const prog = gyp() + prog.parseArgv(['dummy_prog', 'dummy_script', '--target=8.0.0']) + + prog.spawn = function (program, args) { + for (let i = 0; i < args.length; i++) { + const nodedir = process.config.variables.node_prefix + if (checkTargetPath(nodedir, args[i])) { + assert.fail() + } + }; + return SPAWN_RESULT(done) + } + configure(prog, [], assert.fail) + }) + } +}) diff --git a/test/test-configure-python.js b/test/test-configure-python.js index eee230a496..094e79182c 100644 --- a/test/test-configure-python.js +++ b/test/test-configure-python.js @@ -11,6 +11,7 @@ const configure = requireInject('../lib/configure', { 'graceful-fs': { openSync: () => 0, closeSync: () => {}, + existsSync: () => {}, promises: { stat: async () => ({}), mkdir: async () => {},