From 6d5ee8fd8cdc35ad60a86f869a153687c7479389 Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Fri, 24 Jul 2020 12:54:25 -0700 Subject: [PATCH] feat: support gyp with ninja --- addon.gypi | 8 +++ lib/build.js | 22 ++++++-- lib/configure.js | 4 +- lib/install.js | 144 ++++++++++++++++++++++++++++++++++++----------- lib/node-gyp.js | 4 +- 5 files changed, 142 insertions(+), 40 deletions(-) diff --git a/addon.gypi b/addon.gypi index 9327b0d722..1ba5ffa65a 100644 --- a/addon.gypi +++ b/addon.gypi @@ -169,6 +169,14 @@ # clients of class 'node::ObjectWrap' 4251 ], + 'configurations': { + 'Debug_x64': { + 'inherit_from': ['Debug'], + }, + 'Release_x64': { + 'inherit_from': ['Release'], + } + } }, { # OS!="win" 'defines': [ diff --git a/lib/build.js b/lib/build.js index c2388fb348..61352ace70 100644 --- a/lib/build.js +++ b/lib/build.js @@ -19,8 +19,6 @@ function build (gyp, argv, callback) { }) } - var makeCommand = gyp.opts.make || process.env.MAKE || platformMake - var command = win ? 'msbuild' : makeCommand var jobs = gyp.opts.jobs || process.env.JOBS var buildType var config @@ -28,6 +26,13 @@ function build (gyp, argv, callback) { var nodeDir var guessedSolution + const makeCommand = gyp.opts.make || process.env.MAKE || platformMake + let command = win ? 'msbuild' : makeCommand + if (gyp.opts.ninja) { + const ninjaPath = gyp.opts['ninja-path'] || path.resolve(gyp.devDir, 'ninja') + command = path.resolve(ninjaPath, 'ninja') + } + loadConfigGypi() /** @@ -127,8 +132,12 @@ function build (gyp, argv, callback) { var verbose = log.levels[log.level] <= log.levels.verbose var j - if (!win && verbose) { - argv.push('V=1') + if (verbose) { + if (gyp.opts.ninja) { + argv.push('-v') + } else if (!win) { + argv.push('V=1') + } } if (win && !verbose) { @@ -141,7 +150,10 @@ function build (gyp, argv, callback) { } // Specify the build type, Release by default - if (win) { + if (gyp.opts.ninja) { + argv.push('-C') + argv.push(path.join('build', buildType)) + } else if (win) { // Convert .gypi config target_arch to MSBuild /Platform // Since there are many ways to state '32-bit Intel', default to it. // N.B. msbuild's Condition string equality tests are case-insensitive. diff --git a/lib/configure.js b/lib/configure.js index d4342b9d76..8bb60ca066 100644 --- a/lib/configure.js +++ b/lib/configure.js @@ -225,7 +225,9 @@ function configure (gyp, argv, callback) { return callback(err) } - if (!~argv.indexOf('-f') && !~argv.indexOf('--format')) { + if (!gyp.opts.ninja) { + argv.push('-f', 'ninja') + } else 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 diff --git a/lib/install.js b/lib/install.js index f9fa2b34bd..35e6d2a008 100644 --- a/lib/install.js +++ b/lib/install.js @@ -11,10 +11,14 @@ const request = require('request') const processRelease = require('./process-release') const win = process.platform === 'win32' const getProxyFromURI = require('./proxy') +const { spawn } = require('child_process') function install (fs, gyp, argv, callback) { var release = processRelease(argv, gyp, process.version, process.release) + const ninjaPath = gyp.opts['ninja-path'] || path.resolve(gyp.devDir, 'ninja') + const ninjaUrl = 'https://github.com/ninja-build/ninja/archive/v1.10.0.tar.gz' + // ensure no double-callbacks happen function cb (err) { if (cb.done) { @@ -62,42 +66,62 @@ function install (fs, gyp, argv, callback) { // the directory where the dev files will be installed var devDir = path.resolve(gyp.devDir, release.versionDir) - // If '--ensure' was passed, then don't *always* install the version; - // check if it is already installed, and only install when needed - if (gyp.opts.ensure) { - log.verbose('install', '--ensure was passed, so won\'t reinstall if already installed') - fs.stat(devDir, function (err) { - if (err) { - if (err.code === 'ENOENT') { - log.verbose('install', 'version not already installed, continuing with install', release.version) - go() - } else if (err.code === 'EACCES') { - eaccesFallback(err) - } else { - cb(err) - } - return - } - log.verbose('install', 'version is already installed, need to check "installVersion"') - var installVersionFile = path.resolve(devDir, 'installVersion') - fs.readFile(installVersionFile, 'ascii', function (err, ver) { - if (err && err.code !== 'ENOENT') { - return cb(err) - } - var installVersion = parseInt(ver, 10) || 0 - log.verbose('got "installVersion"', installVersion) - log.verbose('needs "installVersion"', gyp.package.installVersion) - if (installVersion < gyp.package.installVersion) { - log.verbose('install', 'version is no good; reinstalling') - go() - } else { - log.verbose('install', 'version is good') - cb() + function handleInstall (err) { + if (err) cb(err) + + // If '--ensure' was passed, then don't *always* install the version; + // check if it is already installed, and only install when needed + if (gyp.opts.ensure) { + log.verbose('install', '--ensure was passed, so won\'t reinstall if already installed') + fs.stat(devDir, function (err) { + if (err) { + if (err.code === 'ENOENT') { + log.verbose('install', 'version not already installed, continuing with install', release.version) + go() + } else if (err.code === 'EACCES') { + eaccesFallback(err) + } else { + cb(err) + } + return } + log.verbose('install', 'version is already installed, need to check "installVersion"') + var installVersionFile = path.resolve(devDir, 'installVersion') + fs.readFile(installVersionFile, 'ascii', function (err, ver) { + if (err && err.code !== 'ENOENT') { + return cb(err) + } + var installVersion = parseInt(ver, 10) || 0 + log.verbose('got "installVersion"', installVersion) + log.verbose('needs "installVersion"', gyp.package.installVersion) + if (installVersion < gyp.package.installVersion) { + log.verbose('install', 'version is no good; reinstalling') + go() + } else { + log.verbose('install', 'version is good') + cb() + } + }) }) + } else { + go() + } + } + + if (gyp.opts.ninja) { + downloadNinja((err, alreadyExists) => { + if (err) return cb(err) + + if (alreadyExists) { + log.verbose('ninja', 'appears to be previously installed') + handleInstall() + } else { + log.verbose('ninja', 'building...') + buildNinja(handleInstall) + } }) } else { - go() + handleInstall() } function getContentSha (res, callback) { @@ -297,7 +321,7 @@ function install (fs, gyp, argv, callback) { } function downloadNodeLib (done) { - log.verbose('on Windows; need to download `' + release.name + '.lib`...') + log.verbose('on Windows need to download `' + release.name + '.lib`...') var archs = ['ia32', 'x64', 'arm64'] var async = archs.length archs.forEach(function (arch) { @@ -352,6 +376,60 @@ function install (fs, gyp, argv, callback) { }) // mkdir() } // go() + function downloadNinja (done) { + if (fs.existsSync(ninjaPath) && gyp.opts.ensure) { + return done(null, true) + } + + log.verbose('ninja', `url: ${ninjaUrl}`) + log.verbose('ninja', 'downloading...') + + request({ strictSSL: false, url: ninjaUrl }) + .on('response', (res) => { + if (res.statusCode !== 200) { + const error = new Error(`Ninja failed downloading tarball: ${res.statusCode}`) + return done(error) + } + + log.verbose('ninja', `http status: ${res.statusCode}`) + function logger (path) { + const name = path.substring(ninjaPath.length + 1).replace(/[.]+?\//, '') + if (name.length > 0) { + log.verbose('ninja extracting: ', name) + } + return true + } + + fs.mkdirSync(ninjaPath) + res.pipe(tar.extract({ + cwd: ninjaPath, + strip: 1, + filter: logger + }).on('end', done).on('error', done)) + }) + .on('error', done) + } + + function buildNinja (done) { + const bootstrap = spawn('python', ['configure.py', '--bootstrap'], { + cwd: ninjaPath, + env: process.env + }) + + bootstrap.on('close', (code) => { + const error = 'Failed to build Ninja' + if (code !== 0) done(error) + done() + }) + + const dataFn = (data) => { + log.verbose('ninja bootstrap', data.toString().trim()) + } + + bootstrap.stdout.on('data', dataFn) + bootstrap.stderr.on('data', dataFn) + } + /** * Checks if a given filename is "valid" for this installation. */ diff --git a/lib/node-gyp.js b/lib/node-gyp.js index 81fc590919..547f1b72ee 100644 --- a/lib/node-gyp.js +++ b/lib/node-gyp.js @@ -75,7 +75,9 @@ proto.configDefs = { 'dist-url': String, // 'install' tarball: String, // 'install' jobs: String, // 'build' - thin: String // 'configure' + thin: String, // 'configure' + ninja: String, // 'everywhere + 'ninja-path': String } /**