@@ -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