From eb2c709cd807759f819050956b2de10f2c76f974 Mon Sep 17 00:00:00 2001 From: Pavel Medvedev Date: Sat, 18 Jun 2016 14:36:58 +0300 Subject: [PATCH] Add gyp.js (https://github.com/indutny/gyp.js) support See issue #960 Added initial `gyp.js` support with --gypjs command line option. Environment variable `npm_config_gypjs` also turns this option on. Update configure and build usage strings depending on `npm_config_gypjs` environment variable. Set `npm_config_gypjs` env variable if `--gypjs` command-line option was set to affect usage text for `configure` and `build` commands. Update usage strings if `--gypjs` command-line option was supplied Trying to load gyp.js module only if --gypjs command-line option was supplied. --- README.md | 33 +++++++++++++++ lib/build.js | 105 +++++++++++++++++++++++++++------------------- lib/configure.js | 106 ++++++++++++++++++++++++++++------------------- lib/node-gyp.js | 4 ++ 4 files changed, 162 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 5212b124f9..7b09bbf2e1 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,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 ---------------------- @@ -181,6 +212,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/lib/build.js b/lib/build.js index 6291af91f3..269d6e2dd7 100644 --- a/lib/build.js +++ b/lib/build.js @@ -15,7 +15,8 @@ var fs = require('graceful-fs') , 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,6 +37,9 @@ function build (gyp, argv, callback) { , arch , nodeDir + if (gyp.opts.gypjs) { + command = gyp.opts.ninja || process.env.NINJA || 'ninja' + } loadConfigGypi() /** @@ -70,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() @@ -102,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 { @@ -185,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 fe94246320..a0734a6d14 100644 --- a/lib/configure.js +++ b/lib/configure.js @@ -21,9 +21,10 @@ var fs = require('graceful-fs') , execFile = cp.execFile , 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,9 +136,10 @@ 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 @@ -185,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 @@ -237,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)) } } @@ -258,11 +272,12 @@ function configure (gyp, argv, callback) { } var nodeGypDir = path.resolve(__dirname, '..') var nodeLibFile = path.join(nodeDir, - !gyp.opts.nodedir ? '<(target_arch)' : '$(Configuration)', + !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) @@ -284,15 +299,20 @@ 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 = new PathArray(process.env, 'PYTHONPATH') - pypath.unshift(path.join(__dirname, '..', 'gyp', 'pylib')) + // make sure python uses files that came with this particular node package + var pypath = new PathArray(process.env, 'PYTHONPATH') + pypath.unshift(path.join(__dirname, '..', 'gyp', 'pylib')) - 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) + onCpExit(gypjs.main(argv)) + } }) } diff --git a/lib/node-gyp.js b/lib/node-gyp.js index 3b8f5b0a1c..637ed9d25c 100644 --- a/lib/node-gyp.js +++ b/lib/node-gyp.js @@ -99,6 +99,7 @@ proto.configDefs = { , 'tarball': String // 'install' , jobs: String // 'build' , thin: String // 'configure' + , gypjs: Boolean // 'configure', 'build' } /** @@ -175,6 +176,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() }