@@ -11,10 +11,14 @@ const request = require('request')
1111const processRelease = require ( './process-release' )
1212const win = process . platform === 'win32'
1313const getProxyFromURI = require ( './proxy' )
14+ const { spawn } = require ( 'child_process' )
1415
1516function install ( fs , gyp , argv , callback ) {
1617 var release = processRelease ( argv , gyp , process . version , process . release )
1718
19+ const ninjaPath = gyp . opts [ 'ninja-path' ] || path . resolve ( gyp . devDir , 'ninja' )
20+ const ninjaUrl = 'https://github.com/ninja-build/ninja/archive/v1.10.0.tar.gz'
21+
1822 // ensure no double-callbacks happen
1923 function cb ( err ) {
2024 if ( cb . done ) {
@@ -62,42 +66,62 @@ function install (fs, gyp, argv, callback) {
6266 // the directory where the dev files will be installed
6367 var devDir = path . resolve ( gyp . devDir , release . versionDir )
6468
65- // If '--ensure' was passed, then don't *always* install the version;
66- // check if it is already installed, and only install when needed
67- if ( gyp . opts . ensure ) {
68- log . verbose ( 'install' , '--ensure was passed, so won\'t reinstall if already installed' )
69- fs . stat ( devDir , function ( err ) {
70- if ( err ) {
71- if ( err . code === 'ENOENT' ) {
72- log . verbose ( 'install' , 'version not already installed, continuing with install' , release . version )
73- go ( )
74- } else if ( err . code === 'EACCES' ) {
75- eaccesFallback ( err )
76- } else {
77- cb ( err )
78- }
79- return
80- }
81- log . verbose ( 'install' , 'version is already installed, need to check "installVersion"' )
82- var installVersionFile = path . resolve ( devDir , 'installVersion' )
83- fs . readFile ( installVersionFile , 'ascii' , function ( err , ver ) {
84- if ( err && err . code !== 'ENOENT' ) {
85- return cb ( err )
86- }
87- var installVersion = parseInt ( ver , 10 ) || 0
88- log . verbose ( 'got "installVersion"' , installVersion )
89- log . verbose ( 'needs "installVersion"' , gyp . package . installVersion )
90- if ( installVersion < gyp . package . installVersion ) {
91- log . verbose ( 'install' , 'version is no good; reinstalling' )
92- go ( )
93- } else {
94- log . verbose ( 'install' , 'version is good' )
95- cb ( )
69+ function handleInstall ( err ) {
70+ if ( err ) cb ( err )
71+
72+ // If '--ensure' was passed, then don't *always* install the version;
73+ // check if it is already installed, and only install when needed
74+ if ( gyp . opts . ensure ) {
75+ log . verbose ( 'install' , '--ensure was passed, so won\'t reinstall if already installed' )
76+ fs . stat ( devDir , function ( err ) {
77+ if ( err ) {
78+ if ( err . code === 'ENOENT' ) {
79+ log . verbose ( 'install' , 'version not already installed, continuing with install' , release . version )
80+ go ( )
81+ } else if ( err . code === 'EACCES' ) {
82+ eaccesFallback ( err )
83+ } else {
84+ cb ( err )
85+ }
86+ return
9687 }
88+ log . verbose ( 'install' , 'version is already installed, need to check "installVersion"' )
89+ var installVersionFile = path . resolve ( devDir , 'installVersion' )
90+ fs . readFile ( installVersionFile , 'ascii' , function ( err , ver ) {
91+ if ( err && err . code !== 'ENOENT' ) {
92+ return cb ( err )
93+ }
94+ var installVersion = parseInt ( ver , 10 ) || 0
95+ log . verbose ( 'got "installVersion"' , installVersion )
96+ log . verbose ( 'needs "installVersion"' , gyp . package . installVersion )
97+ if ( installVersion < gyp . package . installVersion ) {
98+ log . verbose ( 'install' , 'version is no good; reinstalling' )
99+ go ( )
100+ } else {
101+ log . verbose ( 'install' , 'version is good' )
102+ cb ( )
103+ }
104+ } )
97105 } )
106+ } else {
107+ go ( )
108+ }
109+ }
110+
111+ if ( gyp . opts . ninja ) {
112+ downloadNinja ( ( err , alreadyExists ) => {
113+ if ( err ) return cb ( err )
114+
115+ if ( alreadyExists ) {
116+ log . verbose ( 'ninja' , 'appears to be previously installed' )
117+ handleInstall ( )
118+ } else {
119+ log . verbose ( 'ninja' , 'building...' )
120+ buildNinja ( handleInstall )
121+ }
98122 } )
99123 } else {
100- go ( )
124+ handleInstall ( )
101125 }
102126
103127 function getContentSha ( res , callback ) {
@@ -297,7 +321,7 @@ function install (fs, gyp, argv, callback) {
297321 }
298322
299323 function downloadNodeLib ( done ) {
300- log . verbose ( 'on Windows; need to download `' + release . name + '.lib`...' )
324+ log . verbose ( 'on Windows need to download `' + release . name + '.lib`...' )
301325 var archs = [ 'ia32' , 'x64' , 'arm64' ]
302326 var async = archs . length
303327 archs . forEach ( function ( arch ) {
@@ -352,6 +376,60 @@ function install (fs, gyp, argv, callback) {
352376 } ) // mkdir()
353377 } // go()
354378
379+ function downloadNinja ( done ) {
380+ if ( fs . existsSync ( ninjaPath ) && gyp . opts . ensure ) {
381+ return done ( null , true )
382+ }
383+
384+ log . verbose ( 'ninja' , `url: ${ ninjaUrl } ` )
385+ log . verbose ( 'ninja' , 'downloading...' )
386+
387+ request ( { strictSSL : false , url : ninjaUrl } )
388+ . on ( 'response' , ( res ) => {
389+ if ( res . statusCode !== 200 ) {
390+ const error = new Error ( `Ninja failed downloading tarball: ${ res . statusCode } ` )
391+ return done ( error )
392+ }
393+
394+ log . verbose ( 'ninja' , `http status: ${ res . statusCode } ` )
395+ function logger ( path ) {
396+ const name = path . substring ( ninjaPath . length + 1 ) . replace ( / [ . ] + ?\/ / , '' )
397+ if ( name . length > 0 ) {
398+ log . verbose ( 'ninja extracting: ' , name )
399+ }
400+ return true
401+ }
402+
403+ fs . mkdirSync ( ninjaPath )
404+ res . pipe ( tar . extract ( {
405+ cwd : ninjaPath ,
406+ strip : 1 ,
407+ filter : logger
408+ } ) . on ( 'end' , done ) . on ( 'error' , done ) )
409+ } )
410+ . on ( 'error' , done )
411+ }
412+
413+ function buildNinja ( done ) {
414+ const bootstrap = spawn ( 'python' , [ 'configure.py' , '--bootstrap' ] , {
415+ cwd : ninjaPath ,
416+ env : process . env
417+ } )
418+
419+ bootstrap . on ( 'close' , ( code ) => {
420+ const error = 'Failed to build Ninja'
421+ if ( code !== 0 ) done ( error )
422+ done ( )
423+ } )
424+
425+ const dataFn = ( data ) => {
426+ log . verbose ( 'ninja bootstrap' , data . toString ( ) . trim ( ) )
427+ }
428+
429+ bootstrap . stdout . on ( 'data' , dataFn )
430+ bootstrap . stderr . on ( 'data' , dataFn )
431+ }
432+
355433 /**
356434 * Checks if a given filename is "valid" for this installation.
357435 */
0 commit comments