diff --git a/README.md b/README.md index 22415430a0..07c58cebb3 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,37 @@ you can require the `.node` file with Node and run your tests! __Note:__ To create a _Debug_ build of the bindings file, pass the `--debug` (or `-d`) switch when running either the `configure`, `build` or `rebuild` command. +#### Usage without Python + +There is a [`gyp.js`](https://github.com/indutny/gyp.js/) project, a GYP +implementation in JavaScript. It generates [`Ninja`](https://ninja-build.org/) +build files and requires no Python installation. + +In this case you will need to install [`Ninja`](https://ninja-build.org/) build +tool and a C/C++ compiler toolchain. + +To generate projects files with `gyp.js` instead of `gyp` and to build them with +`ninja`, please supply a command-line option `--gypjs` in `node-gyp` command. + +``` bash +$ node-gyp configure --gypjs +``` + +It is also possible to set `npm_config_gypjs` environment variable to turn on +the `gyp.js` usage. + +``` bash +$ npm_config_gypjs=1 node-gyp build +``` + +Path to an existing `ninja` installation can be set with a `--ninja` command-line +option or in a `NINJA` environment variable. + +``` bash +$ node-gyp configure --gypjs --ninja=/my/path/to/ninja +$ npm_config_gypjs=1 NINJA=/my/path/to/ninja node-gyp build +``` + The "binding.gyp" file ---------------------- @@ -184,6 +215,8 @@ Command Options | `--python=$path` | Set path to the python (2) binary | `--msvs_version=$version` | Set Visual Studio version (win) | `--solution=$solution` | Set Visual Studio Solution version (win) +| `--gypjs` | Use gyp.js instead of gyp +| `--ninja=$ninja` | Override ninja command (with --gypjs) License diff --git a/addon.gypi b/addon.gypi index 3be0f591bd..e65746078d 100644 --- a/addon.gypi +++ b/addon.gypi @@ -92,7 +92,7 @@ '-luuid.lib', '-lodbc32.lib', '-lDelayImp.lib', - '-l"<(node_root_dir)/$(ConfigurationName)/<(node_lib_file)"' + '-l"<(node_lib_file)"' ], 'msvs_disabled_warnings': [ # warning C4251: 'node::ObjectWrap::handle_' : class 'v8::Persistent' diff --git a/lib/build.js b/lib/build.js index 22f2583694..00056bf848 100644 --- a/lib/build.js +++ b/lib/build.js @@ -11,12 +11,12 @@ var fs = require('graceful-fs') , glob = require('glob') , log = require('npmlog') , which = require('which') - , mkdirp = require('mkdirp') , exec = require('child_process').exec , processRelease = require('./process-release') , win = process.platform == 'win32' -exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module' +exports.usage = 'Invokes `' + (process.env.npm_config_gypjs ? 'ninja' : + (win ? 'msbuild' : 'make')) + '` and builds the module' function build (gyp, argv, callback) { var platformMake = 'make' @@ -36,8 +36,10 @@ function build (gyp, argv, callback) { , config , arch , nodeDir - , copyDevLib + if (gyp.opts.gypjs) { + command = gyp.opts.ninja || process.env.NINJA || 'ninja' + } loadConfigGypi() /** @@ -60,7 +62,6 @@ function build (gyp, argv, callback) { buildType = config.target_defaults.default_configuration arch = config.variables.target_arch nodeDir = config.variables.nodedir - copyDevLib = config.variables.copy_dev_lib == 'true' if ('debug' in gyp.opts) { buildType = gyp.opts.debug ? 'Debug' : 'Release' @@ -73,7 +74,7 @@ function build (gyp, argv, callback) { log.verbose('architecture', arch) log.verbose('node dev dir', nodeDir) - if (win) { + if (win && !gyp.opts.gypjs) { findSolutionFile() } else { doWhich() @@ -105,7 +106,7 @@ function build (gyp, argv, callback) { // First make sure we have the build command in the PATH which(command, function (err, execPath) { if (err) { - if (win && /not found/.test(err.message)) { + if (win && !gyp.opts.gypjs && /not found/.test(err.message)) { // On windows and no 'msbuild' found. Let's guess where it is findMsbuild() } else { @@ -115,7 +116,7 @@ function build (gyp, argv, callback) { return } log.verbose('`which` succeeded for `' + command + '`', execPath) - copyNodeLib() + doBuild() }) } @@ -173,36 +174,12 @@ function build (gyp, argv, callback) { return } command = msbuildPath - copyNodeLib() + doBuild() }) })() }) } - /** - * Copies the node.lib file for the current target architecture into the - * current proper dev dir location. - */ - - function copyNodeLib () { - if (!win || !copyDevLib) return doBuild() - - var buildDir = path.resolve(nodeDir, buildType) - , archNodeLibPath = path.resolve(nodeDir, arch, release.name + '.lib') - , buildNodeLibPath = path.resolve(buildDir, release.name + '.lib') - - mkdirp(buildDir, function (err, isNew) { - if (err) return callback(err) - log.verbose('"' + buildType + '" dir needed to be created?', isNew) - var rs = fs.createReadStream(archNodeLibPath) - , ws = fs.createWriteStream(buildNodeLibPath) - log.verbose('copying "' + release.name + '.lib" for ' + arch, buildNodeLibPath) - rs.pipe(ws) - rs.on('error', callback) - ws.on('error', callback) - rs.on('end', doBuild) - }) - } /** * Actually spawn the process and compile the module. @@ -212,57 +189,72 @@ function build (gyp, argv, callback) { // Enable Verbose build var verbose = log.levels[log.level] <= log.levels.verbose - if (!win && verbose) { - argv.push('V=1') - } - if (win && !verbose) { - argv.push('/clp:Verbosity=minimal') - } + if (!gyp.opts.gypjs) { + if (!win && verbose) { + argv.push('V=1') + } + if (win && !verbose) { + argv.push('/clp:Verbosity=minimal') + } - if (win) { - // Turn off the Microsoft logo on Windows - argv.push('/nologo') - } + if (win) { + // Turn off the Microsoft logo on Windows + argv.push('/nologo') + } - // Specify the build type, Release by default - if (win) { - var p = arch === 'x64' ? 'x64' : 'Win32' - argv.push('/p:Configuration=' + buildType + ';Platform=' + p) - if (jobs) { - var j = parseInt(jobs, 10) - if (!isNaN(j) && j > 0) { - argv.push('/m:' + j) - } else if (jobs.toUpperCase() === 'MAX') { - argv.push('/m:' + require('os').cpus().length) + // Specify the build type, Release by default + if (win) { + var p = arch === 'x64' ? 'x64' : 'Win32' + argv.push('/p:Configuration=' + buildType + ';Platform=' + p) + if (jobs) { + var j = parseInt(jobs, 10) + if (!isNaN(j) && j > 0) { + argv.push('/m:' + j) + } else if (jobs.toUpperCase() === 'MAX') { + argv.push('/m:' + require('os').cpus().length) + } + } + } else { + argv.push('BUILDTYPE=' + buildType) + // Invoke the Makefile in the 'build' dir. + argv.push('-C') + argv.push('build') + if (jobs) { + var j = parseInt(jobs, 10) + if (!isNaN(j) && j > 0) { + argv.push('--jobs') + argv.push(j) + } else if (jobs.toUpperCase() === 'MAX') { + argv.push('--jobs') + argv.push(require('os').cpus().length) + } + } + } + + if (win) { + // did the user specify their own .sln file? + var hasSln = argv.some(function (arg) { + return path.extname(arg) == '.sln' + }) + if (!hasSln) { + argv.unshift(gyp.opts.solution || guessedSolution) } } } else { - argv.push('BUILDTYPE=' + buildType) - // Invoke the Makefile in the 'build' dir. - argv.push('-C') - argv.push('build') + // build with ninja + if (verbose) { + argv.push('-v') + } + // Specify the build type, Release by default + argv.push('-C', path.join('build', buildType)) if (jobs) { - var j = parseInt(jobs, 10) + var j = jobs.toUpperCase() === 'MAX'? require('os').cpus().length : parseInt(jobs, 10) if (!isNaN(j) && j > 0) { - argv.push('--jobs') - argv.push(j) - } else if (jobs.toUpperCase() === 'MAX') { - argv.push('--jobs') - argv.push(require('os').cpus().length) + argv.push('-j' + j) } } } - if (win) { - // did the user specify their own .sln file? - var hasSln = argv.some(function (arg) { - return path.extname(arg) == '.sln' - }) - if (!hasSln) { - argv.unshift(gyp.opts.solution || guessedSolution) - } - } - var proc = gyp.spawn(command, argv) proc.on('exit', onExit) } diff --git a/lib/configure.js b/lib/configure.js index 54bb2ccb3a..28e37f66cf 100644 --- a/lib/configure.js +++ b/lib/configure.js @@ -21,9 +21,10 @@ var fs = require('graceful-fs') , processRelease = require('./process-release') , win = process.platform == 'win32' , findNodeDirectory = require('./find-node-directory') - , msgFormat = require('util').format + , gypjs = undefined -exports.usage = 'Generates ' + (win ? 'MSVC project files' : 'a Makefile') + ' for the current module' +exports.usage = 'Generates ' + (process.env.npm_config_gypjs ? 'ninja build files' : + (win ? 'MSVC project files' : 'a Makefile')) + ' for the current module' function configure (gyp, argv, callback) { @@ -31,17 +32,28 @@ function configure (gyp, argv, callback) { , buildDir = path.resolve('build') , configNames = [ 'config.gypi', 'common.gypi' ] , configs = [] + , buildType + , arch , nodeDir , release = processRelease(argv, gyp, process.version, process.release) - findPython(python, function (err, found) { - if (err) { - callback(err) - } else { - python = found - getNodeDir() + if (!gyp.opts.gypjs) { + findPython(python, function (err, found) { + if (err) { + callback(err) + } else { + python = found + getNodeDir() + } + }) + } else { + try { + gypjs = require('gyp.js') + } catch (err) { + return callback(new Error('Can\'t find module gyp.js, you can install it with `npm install gyp.js`')) } - }) + getNodeDir() + } function getNodeDir () { @@ -124,16 +136,14 @@ function configure (gyp, argv, callback) { if (!defaults.default_configuration) { defaults.default_configuration = 'Release' } + buildType = defaults.default_configuration // set the target_arch variable - variables.target_arch = gyp.opts.arch || process.arch || 'ia32' + variables.target_arch = arch = gyp.opts.arch || process.arch || 'ia32' // set the node development directory variables.nodedir = nodeDir - // don't copy dev libraries with nodedir option - variables.copy_dev_lib = !gyp.opts.nodedir - // disable -T "thin" static archives by default variables.standalone_static_library = gyp.opts.thin ? 0 : 1 @@ -188,35 +198,37 @@ function configure (gyp, argv, callback) { function runGyp (err) { if (err) return callback(err) - if (!~argv.indexOf('-f') && !~argv.indexOf('--format')) { - if (win) { - log.verbose('gyp', 'gyp format was not specified; forcing "msvs"') - // force the 'make' target for non-Windows - argv.push('-f', 'msvs') - } else { - log.verbose('gyp', 'gyp format was not specified; forcing "make"') - // force the 'make' target for non-Windows - argv.push('-f', 'make') + if (!gyp.opts.gypjs) { + if (!~argv.indexOf('-f') && !~argv.indexOf('--format')) { + if (win) { + log.verbose('gyp', 'gyp format was not specified; forcing "msvs"') + // force the 'msvs' target for non-Windows + argv.push('-f', 'msvs') + } else { + log.verbose('gyp', 'gyp format was not specified; forcing "make"') + // force the 'make' target for non-Windows + argv.push('-f', 'make') + } } - } - function hasMsvsVersion () { - return argv.some(function (arg) { - return arg.indexOf('msvs_version') === 0 - }) - } + function hasMsvsVersion () { + return argv.some(function (arg) { + return arg.indexOf('msvs_version') === 0 + }) + } - if (win && !hasMsvsVersion()) { - if ('msvs_version' in gyp.opts) { - argv.push('-G', 'msvs_version=' + gyp.opts.msvs_version) - } else { - argv.push('-G', 'msvs_version=auto') + if (win && !hasMsvsVersion()) { + if ('msvs_version' in gyp.opts) { + argv.push('-G', 'msvs_version=' + gyp.opts.msvs_version) + } else { + argv.push('-G', 'msvs_version=auto') + } } } // include all the ".gypi" files that were found configs.forEach(function (config) { - argv.push('-I', config) + argv.push('-I' + config) }) // for AIX we need to set up the path to the exp file @@ -240,9 +252,8 @@ function configure (gyp, argv, callback) { if (node_exp_file !== undefined) { log.verbose(logprefix, 'Found exports file: %s', node_exp_file) } else { - var msg = msgFormat('Could not find node.exp file in %s', node_root_dir) log.error(logprefix, 'Could not find exports file') - return callback(new Error(msg)) + return callback(new Error('Could not find node.exp file in ' + node_root_dir)) } } @@ -260,9 +271,13 @@ function configure (gyp, argv, callback) { output_dir = buildDir } var nodeGypDir = path.resolve(__dirname, '..') + var nodeLibFile = path.join(nodeDir, + !gyp.opts.nodedir ? '<(target_arch)' : + (gyp.opts.gypjs ? buildType : '$(Configuration)'), + release.name + '.lib') - argv.push('-I', addon_gypi) - argv.push('-I', common_gypi) + argv.push('-I' + addon_gypi) + argv.push('-I' + common_gypi) argv.push('-Dlibrary=shared_library') argv.push('-Dvisibility=default') argv.push('-Dnode_root_dir=' + nodeDir) @@ -270,7 +285,7 @@ function configure (gyp, argv, callback) { argv.push('-Dnode_exp_file=' + node_exp_file) } argv.push('-Dnode_gyp_dir=' + nodeGypDir) - argv.push('-Dnode_lib_file=' + release.name + '.lib') + argv.push('-Dnode_lib_file=' + nodeLibFile) argv.push('-Dmodule_root_dir=' + process.cwd()) argv.push('--depth=.') argv.push('--no-parallel') @@ -284,18 +299,25 @@ function configure (gyp, argv, callback) { // enforce use of the "binding.gyp" file argv.unshift('binding.gyp') - // execute `gyp` from the current target nodedir - argv.unshift(gyp_script) + if (!gyp.opts.gypjs) { + // execute `gyp` from the current target nodedir + argv.unshift(gyp_script) - // make sure python uses files that came with this particular node package - var pypath = [path.join(__dirname, '..', 'gyp', 'pylib')] - if (process.env.PYTHONPATH) { - pypath.push(process.env.PYTHONPATH) - } - process.env.PYTHONPATH = pypath.join(win ? ';' : ':') + // make sure python uses files that came with this particular node package + var pypath = [path.join(__dirname, '..', 'gyp', 'pylib')] + if (process.env.PYTHONPATH) { + pypath.push(process.env.PYTHONPATH) + } + process.env.PYTHONPATH = pypath.join(win ? ';' : ':') - var cp = gyp.spawn(python, argv) - cp.on('exit', onCpExit) + var cp = gyp.spawn(python, argv) + cp.on('exit', onCpExit) + } else { + argv.push('-Dtarget_arch=' + arch) + log.info('using', 'gyp.js@' + gypjs.version) + log.info('gyp.js args', argv) + onCpExit(gypjs.main(argv, path.join(process.cwd(), 'build', buildType))) + } }) } diff --git a/lib/node-gyp.js b/lib/node-gyp.js index a841161e32..ea0d87c1fa 100644 --- a/lib/node-gyp.js +++ b/lib/node-gyp.js @@ -89,6 +89,7 @@ proto.configDefs = { , 'tarball': String // 'install' , jobs: String // 'build' , thin: String // 'configure' + , gypjs: Boolean // 'configure', 'build' } /** @@ -165,6 +166,9 @@ proto.parseArgv = function parseOpts (argv) { if (this.opts.loglevel) { log.level = this.opts.loglevel } + if (this.opts.gypjs) { + process.env[npm_config_prefix + 'gypjs'] = true + } log.resume() }