Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support gyp with ninja #2171

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions addon.gypi
Expand Up @@ -169,6 +169,14 @@
# clients of class 'node::ObjectWrap'
4251
],
'configurations': {
'Debug_x64': {
'inherit_from': ['Debug'],
},
'Release_x64': {
'inherit_from': ['Release'],
}
}
}, {
# OS!="win"
'defines': [
Expand Down
22 changes: 17 additions & 5 deletions lib/build.js
Expand Up @@ -19,15 +19,20 @@ 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
var arch
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()

/**
Expand Down Expand Up @@ -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) {
Expand All @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion lib/configure.js
Expand Up @@ -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
Expand Down
144 changes: 111 additions & 33 deletions lib/install.js
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
*/
Expand Down
4 changes: 3 additions & 1 deletion lib/node-gyp.js
Expand Up @@ -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
}

/**
Expand Down