osrm
Version:
The Open Source Routing Machine is a high performance routing engine written in C++14 designed to run on OpenStreetMap data.
349 lines (327 loc) • 11.4 kB
JavaScript
var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;
var yargs = require('yargs');
var which = require('which');
var debug = require('debug')('ncmake');
// Main usage strings
var argparse = yargs
.usage('$0 [options] <command>');
// Primary options
// Where appropriate, node-gyp argument names have been retained for
// compatibility. All unsupported options will be ignored by the parser.
argparse = argparse
.global(['debug', 'target', 'dist-url'])
.describe('debug', 'Build with debug symbols')
.describe('target', 'Version of Node.js to use')
.describe('dist-url', 'Set the download server for dependencies');
// New options unique to CMake / ncmake
argparse = argparse
.global(['name', 'output', 'generator', 'arch'])
.describe('name', 'The executable target (node/iojs)')
.describe('output', 'The build directory')
.describe('generator', 'The CMake generator to use')
.describe('arch', 'The target architecture');
// Commands (task to execute)
argparse = argparse
.command('help', 'Shows the help dialog')
.command('build', 'Builds the native addon')
.command('clean', 'Cleans the build')
.command('distclean', 'Removes all build files')
.command('configure', 'Runs CMake to generate the project configuration')
.command('rebuild', 'Runs clean, configure and build')
.command('update', 'Copies the NodeJS.cmake from the installed module')
.command('install', 'Install the native addon');
// Deprecated commands from node-gyp
var compat = 'Deprecated node-gyp command (no-op)';
argparse = argparse
.command('list', compat)
.command('remove', compat);
// Mark advanced options
argparse = argparse
.group(['target', 'dist-url', 'name', 'output', 'generator', 'arch'], 'Advanced:');
// Aliases and settings for the options
argparse = argparse
.boolean('debug')
.alias('debug', 'd')
.alias('help', 'h')
.alias('output', 'o')
.alias('generator', 'g')
.alias('arch', 'a');
// Use Ninja on platforms where it is installed as a default
// (since its significantly faster than make)
var ninja, generator = 'default';
if(process.platform === 'darwin' || process.platform == 'linux') {
try {
ninja = which.sync('ninja');
generator = 'Ninja';
}
catch(err) {}
}
debug('Selecting default generator "' + generator + '"');
// Architecture detection parameters
var msvcRegex = /visual\s+studio/i;
var msvcArch = {
x64: 'x64',
ia32: 'x86',
x32: 'x86'
};
// Defaults for options that need them
argparse = argparse
.default('output', 'build')
.default('generator', generator)
.default('debug', false)
.default('arch', process.arch);
// Exactly one command must be specified
argparse = argparse.demandCommand(1, 1);
// Support a version string, and a help argument (in addition to
// the help command)
argparse = argparse.version();
argparse = argparse.help();
// Warning type - used to differentiate output in promise chain
function Warning(msg) { this.message = msg; }
// Catch-all function for node-gyp deprecated commands
function deprecated() {
return new Promise(function (resolve, reject) {
var warn = new Warning('node-gyp deprecated command invoked. ' +
'This is not supported in node-cmake, consider updating your build');
warn.code = 0; // Exit cleanly (for tools that still use this command)
reject(warn);
});
}
// The list of accepted commands (mirror node-gyp's API)
var commands = {
help: function () {
return new Promise(function (resolve) {
argparse.showHelp();
resolve();
});
},
distclean: function (argv, cmake) {
return new Promise(function (resolve, reject) {
var args = ['-E', 'remove_directory', argv.output];
debug('Execute', cmake, args);
var distclean = spawn(cmake, args, {
stdio: 'inherit'
});
function handleError(code) {
if(code !== 0) { // An Error object will also not equal 0
var err = new Error('Unable to remove build directory');
err.code = 8;
return reject(err);
}
return resolve();
}
distclean.on('exit' , handleError);
distclean.on('error', handleError);
});
},
clean: function (argv, cmake) {
// Run CMake clean if the project has been "configured"
var args = ['--build', argv.output, '--target', 'clean'];
args.push('--config', (argv.debug) ? 'Debug' : 'Release');
return new Promise(function (resolve, reject) {
fs.exists(path.join(argv.output, 'CMakeCache.txt'), function (exists) {
if(exists) {
// Silently clean the project, do nothing on faiure (no-op)
debug('Execute', cmake, args);
var clean = spawn(cmake, args, {
stdio: 'ignore'
});
function handleError(code) { return resolve(); }
clean.on('exit', handleError);
clean.on('error', handleError);
}
else resolve();
});
});
},
configure: function (argv, cmake) {
var args = ['-DCMAKE_BUILD_TYPE=' + ((argv.debug) ? 'Debug' : 'Release')];
// Under MSVC specifically, attempt to pass the architecture flag of the current version
// of node to cmake. This should attempt to use an MSVC that will compile node modules
// compatible with this version
// From: https://gitlab.kitware.com/cmake/cmake/issues/16339
if (process.platform === 'win32' &&
(argv.generator === 'default' || msvcRegex.test(argv.generator)) && msvcArch[argv.arch]) {
args.push('-A');
args.push(msvcArch[argv.arch]);
}
if(argv.generator !== 'default') args.push('-G', argv.generator);
if(argv.target) args.push('-DNODEJS_VERSION=' + argv.target);
if(argv.distUrl) args.push('-DNODEJS_URL="' + argv.distUrl + '"');
if(argv.name) args.push('-DNODEJS_NAME="' + argv.name + '"');
args.push.apply(args, argv._.slice(1)); // Include any additional arguments passed to ncmake
args.push('..');
return new Promise(function (resolve, reject) {
// Use CMake as a cross-platform mkdir to create the build directory
var margs = ['-E', 'make_directory', argv.output];
debug('Execute', cmake, margs);
var mkdir = spawn(cmake, margs, {
stdio: 'inherit'
});
function handleError(code) {
if(code !== 0) {
var err = new Error('Unable to create build directory');
err.code = 3;
return reject(err);
}
return resolve();
}
mkdir.on('exit', handleError);
mkdir.on('error', handleError);
}).then(function () {
return new Promise(function (resolve, reject) {
// Run CMake to configure the project
debug('Execute', cmake, args);
var configure = spawn(cmake, args, {
cwd: path.resolve(argv.output),
stdio: 'inherit'
});
function handleError(code) {
if(code !== 0) {
var err = new Error('Unable to configure project');
err.code = 4;
return reject(err);
}
return resolve();
}
configure.on('exit', handleError);
configure.on('error', handleError);
});
});
},
build: function (argv, cmake) {
// Run CMake build to build the project (generator agnostic)
var args = ['--build', argv.output, '--config', (argv.debug) ? 'Debug' : 'Release'];
return new Promise(function (resolve, reject) {
fs.exists(path.join(argv.output, 'CMakeCache.txt'), function (exists) {
if(exists) {
debug('Execute', cmake, args);
var build = spawn(cmake, args, {
stdio: 'inherit'
});
function handleError(code) {
if(code !== 0) {
var err = new Error('Build failed');
err.code = 7;
return reject(err);
}
return resolve();
}
build.on('exit', handleError);
build.on('error', handleError);
}
else {
var err = new Error('Project is not configured, ' +
'Run \'configure\' command first');
err.code = 6;
return reject(err);
}
});
});
},
rebuild: function (argv, cmake) {
// Per node-gyp, run clean, then configure, then build
return commands.clean(argv, cmake)
.then(function () {
return commands.configure(argv, cmake);
})
.then(function () {
return commands.build(argv, cmake);
});
},
update: function (argv, cmake) {
return new Promise(function (resolve, reject) {
// The CMake script is relative to this utility when installed
var source = path.resolve(path.join(__dirname, '..', 'NodeJS.cmake'));
var output = path.resolve('NodeJS.cmake');
var rd = fs.createReadStream(source);
rd.on('error', function (rerr) {
var err = new Error('Unable to read NodeJS.cmake');
err.code = 9;
reject(err);
});
var wr = fs.createWriteStream(output);
wr.on('error', function (rerr) {
var err = new Error('Unable to write NodeJS.cmake');
err.code = 9;
reject(err);
});
wr.on('close', function (rerr) {
if(rerr) {
var err = new Error('Unknown I/O error');
err.code = 9;
reject(err);
}
resolve();
});
rd.pipe(wr);
});
},
install: function (argv, cmake) {
// Run CMake build to install the project (generator agnostic)
var args = [
'--build', argv.output, '--config', (argv.debug) ? 'Debug' : 'Release',
'--target', 'install'
];
return new Promise(function (resolve, reject) {
fs.exists(path.join(argv.output, 'CMakeCache.txt'), function (exists) {
if (exists) {
debug('Execute', cmake, args);
var build = spawn(cmake, args, {
stdio: 'inherit'
});
function handleError(code) {
if (code !== 0) {
var err = new Error('Build failed');
err.code = 7;
return reject(err);
}
return resolve();
}
build.on('exit', handleError);
build.on('error', handleError);
}
else {
var err = new Error('Project is not configured, ' +
'Run \'configure\' command first');
err.code = 6;
return reject(err);
}
});
});
},
list: deprecated,
remove: deprecated
};
// Find the cmake binary on the user's path
var cmake;
try {
cmake = which.sync('cmake');
}
catch(e) {
console.error('CMake binary could not be found. Please verify your PATH.');
process.exit(127);
}
// Finalize command line arguments
var argv = argparse.argv;
debug('Arguments', argv);
// Parse the first plain-argument as the command to execute
var cmd = argparse.argv._[0].toLowerCase();
debug('Command', cmd);
var func = commands[cmd];
((func) ? func(argv, cmake) : Promise.reject(
new Error('Invalid command \'' + cmd + '\'')
))
// On success, exit cleanly
.then(function () {
process.exit(0);
})
// Otherwise, log the error and exit with a status code
.catch(function (err) {
if(err instanceof Warning) console.warn(err.message);
else console.error(err.message);
process.exit(err.code || 2);
});