Skip to content

Instantly share code, notes, and snippets.

@aleclarson
Last active September 21, 2020 03:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aleclarson/71ddec23c9e9ca3679f75c17addb1a44 to your computer and use it in GitHub Desktop.
Save aleclarson/71ddec23c9e9ca3679f75c17addb1a44 to your computer and use it in GitHub Desktop.
node-gyp build.js

Build the native binding of a NodeJS library for multiple platforms/versions.

With no arguments, this script use the nearest node-gyp installation to build binding.gyp for the version of node being used. Then it looks for binding.node in the resulting build/Release directory, and copies it into the vendor directory (created on-the-fly). You will find it inside a subdirectory named ${process.platform}-${process.arch}-${moduleVersion}.

When --target or --version are undefined, this script resolves their values for you.

NOTE: You must specify --version if you specify --target as a version not currently used by your shell.

After building the native binding, you can npm rm bindings and use the provided binding.js module for loading your native binding with require. The binding.js module is smart enough to know which vendor subdirectory contains the appropriate binding for the current process.

// Where `name` is the same value you passed to `--name`.
var binding = require('./binding')(name);

Options

  • -h --help Displays the supported options
  • -n --name The file path to the native binding (defaults to "binding")
  • -p --platform Either "node" or "electron" (defaults to "node")
  • -t --target The platform version (optional)
  • --version The native module version (optional)

Examples

# Installation
curl -L -o ./build.js https://gist.githubusercontent.com/aleclarson/71ddec23c9e9ca3679f75c17addb1a44/raw/c58bb46d091e3375da48720b44ec3a9aec90675f/build.js &&
chmod +x build.js

# Build for nearest Electron version
./build.js -p electron

# Look for `some-lib.node` when copying into `vendor`
./build.js -n some-lib

# Build for a NodeJS version not being used by your shell
./build.js --target 7.10.1 --version 51
var path = require('path');
var binaryName = [
process.platform,
process.arch,
process.versions.modules,
].join('-');
module.exports = function(name) {
var binaryPath = path.join(__dirname, 'vendor', binaryName, (name || 'binding') + '.node');
return require(binaryPath);
};
#!/usr/bin/env node
var {spawn} = require('child_process');
var path = require('path');
var fs = require('fs');
if (hasArgument('-h') || hasArgument('--help')) {
return console.log('\n' + [
'-n --name The file path to the native binding (defaults to "binding")',
'-p --platform Either "node" or "electron" (defaults to "node")',
'-t --target The platform version (optional)',
'--version The module version (optional)',
].join('\n') + '\n');
}
build();
//
// Helpers
//
function build() {
var bindingName = getArgument('-n') || getArgument('--name') || 'binding';
var platform = getArgument('-p') || getArgument('--platform') || 'node';
var target = getArgument('-t') || getArgument('--target') || getTarget(platform);
console.log('');
console.log('bindingPath = ' + bindingName);
console.log('platform = ' + platform);
console.log('target = ' + target);
console.log('arch = ' + process.arch);
console.log('');
var gypPath = require.resolve(path.join('node-gyp', 'bin', 'node-gyp.js'));
var args = [
gypPath,
'rebuild',
'--target=' + target,
'--arch=' + process.arch,
];
if (platform === 'electron') {
args.push('--dist-url=https://atom.io/download/electron');
}
console.log('Building...');
console.log(process.execPath + ' ' + args.join(' '));
console.log('');
var proc = spawn(process.execPath, args, {
stdio: ['ignore', 'ignore', 2]
});
proc.on('exit', function(errorCode) {
if (!errorCode) {
var buildPath = path.resolve(__dirname, '..', 'build', 'Release');
var bindingPath = path.join(buildPath, bindingName + '.node');
if (!fs.existsSync(bindingPath)) {
console.error('Binding does not exist: ' + bindingPath);
process.exit(1);
}
var vendorPath = path.resolve(__dirname, '..', 'vendor');
if (!isDirectory(vendorPath)) {
fs.mkdirSync(vendorPath);
console.log('Created directory: ' + vendorPath);
}
return getModuleVersion(platform).then(function(moduleVersion) {
console.log('moduleVersion = ' + moduleVersion);
var targetName = [
process.platform, '-',
process.arch, '-',
moduleVersion
].join('');
var targetPath = path.join(vendorPath, targetName);
if (!isDirectory(targetPath)) {
fs.mkdirSync(targetPath);
console.log('Created directory: ' + targetPath);
}
targetPath = path.join(targetPath, bindingName + '.node');
fs.writeFileSync(targetPath, fs.readFileSync(bindingPath));
console.log('Copied binding: ' + targetPath);
}, function(error) {
console.error('Failed to get Electron\'s module version');
throw error;
})
.then(process.exit, function(error) {
console.error(error);
process.exit(1);
});
}
if (errorCode === 127 ) {
console.error('node-gyp not found!');
} else {
console.error('Build failed with error code: ' + errorCode);
}
process.exit(1);
});
}
function hasArgument(name) {
var args = process.argv.slice(2);
return args.lastIndexOf(name) >= 0;
}
function getArgument(name) {
var args = process.argv.slice(2);
var index = args.lastIndexOf(name);
if (~index) {
return args[index + 1] || null;
}
return null;
}
function getTarget(platform) {
if (platform === 'node') {
return process.versions.node;
} else if (platform === 'electron') {
return getElectronVersion();
} else {
console.error('Unsupported platform: ' + platform);
process.exit(1);
}
}
function getModuleVersion(platform) {
var version = getArgument('--version');
if (version) {
return Promise.resolve(version);
}
if (platform === 'node') {
return Promise.resolve(process.versions.modules);
}
if (platform === 'electron') {
return getElectronModuleVersion();
}
}
function getElectronVersion() {
var electronRoot = path.dirname(require.resolve('electron'));
var config = require(path.join(electronRoot, 'package.json'));
console.log('Electron path: ' + electronRoot);
console.log('Electron version: ' + config.version);
return config.version;
}
function getElectronModuleVersion() {
var electronRoot = path.dirname(require.resolve('electron'));
var electronPath = path.join(electronRoot, 'path.txt');
electronPath = path.join(electronRoot, fs.readFileSync(electronPath, 'utf8'));
return new Promise(function(resolve) {
var args = ['-e', '"console.log(process.versions.modules)"'];
var opts = {env: {ELECTRON_RUN_AS_NODE: '1'}};
console.log('Spawning: ' + [electronPath].concat(args).join(' '));
var proc = spawn(electronPath, args, opts);
var stdout = [];
proc.stdout.on('data', function(data) {
stdout.push(data.toString());
});
var stderr = [];
proc.stderr.on('data', function(data) {
stderr.push(data.toString());
});
proc.on('exit', function() {
if (stderr.length) {
console.error('Failed to get Electron\'s module version: ' + stderr.join(''));
process.exit(1);
}
if (!stdout.length) {
console.error('Failed to get Electron\'s module version');
process.exit(1);
}
return resolve(stdout.join(''));
});
});
}
function isDirectory(filePath) {
var result;
try {
result = fs.statSync(filePath).isDirectory();
} catch(error) {}
return !!result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment