"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/node-gyp/lib/install.js" (11 Apr 2017, 14969 Bytes) of package /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Javascript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 
    2 module.exports = exports = install
    3 
    4 module.exports.test = { download: download, readCAFile: readCAFile }
    5 
    6 exports.usage = 'Install node development files for the specified node version.'
    7 
    8 /**
    9  * Module dependencies.
   10  */
   11 
   12 var fs = require('graceful-fs')
   13   , osenv = require('osenv')
   14   , tar = require('tar')
   15   , rm = require('rimraf')
   16   , path = require('path')
   17   , crypto = require('crypto')
   18   , zlib = require('zlib')
   19   , log = require('npmlog')
   20   , semver = require('semver')
   21   , fstream = require('fstream')
   22   , request = require('request')
   23   , minimatch = require('minimatch')
   24   , mkdir = require('mkdirp')
   25   , processRelease = require('./process-release')
   26   , win = process.platform == 'win32'
   27 
   28 function install (gyp, argv, callback) {
   29 
   30   var release = processRelease(argv, gyp, process.version, process.release)
   31 
   32   // ensure no double-callbacks happen
   33   function cb (err) {
   34     if (cb.done) return
   35     cb.done = true
   36     if (err) {
   37       log.warn('install', 'got an error, rolling back install')
   38       // roll-back the install if anything went wrong
   39       gyp.commands.remove([ release.versionDir ], function (err2) {
   40         callback(err)
   41       })
   42     } else {
   43       callback(null, release.version)
   44     }
   45   }
   46 
   47   // Determine which node dev files version we are installing
   48   log.verbose('install', 'input version string %j', release.version)
   49 
   50   if (!release.semver) {
   51     // could not parse the version string with semver
   52     return callback(new Error('Invalid version number: ' + release.version))
   53   }
   54 
   55   if (semver.lt(release.version, '0.8.0')) {
   56     return callback(new Error('Minimum target version is `0.8.0` or greater. Got: ' + release.version))
   57   }
   58 
   59   // 0.x.y-pre versions are not published yet and cannot be installed. Bail.
   60   if (release.semver.prerelease[0] === 'pre') {
   61     log.verbose('detected "pre" node version', release.version)
   62     if (gyp.opts.nodedir) {
   63       log.verbose('--nodedir flag was passed; skipping install', gyp.opts.nodedir)
   64       callback()
   65     } else {
   66       callback(new Error('"pre" versions of node cannot be installed, use the --nodedir flag instead'))
   67     }
   68     return
   69   }
   70 
   71   // flatten version into String
   72   log.verbose('install', 'installing version: %s', release.versionDir)
   73 
   74   // the directory where the dev files will be installed
   75   var devDir = path.resolve(gyp.devDir, release.versionDir)
   76 
   77   // If '--ensure' was passed, then don't *always* install the version;
   78   // check if it is already installed, and only install when needed
   79   if (gyp.opts.ensure) {
   80     log.verbose('install', '--ensure was passed, so won\'t reinstall if already installed')
   81     fs.stat(devDir, function (err, stat) {
   82       if (err) {
   83         if (err.code == 'ENOENT') {
   84           log.verbose('install', 'version not already installed, continuing with install', release.version)
   85           go()
   86         } else if (err.code == 'EACCES') {
   87           eaccesFallback()
   88         } else {
   89           cb(err)
   90         }
   91         return
   92       }
   93       log.verbose('install', 'version is already installed, need to check "installVersion"')
   94       var installVersionFile = path.resolve(devDir, 'installVersion')
   95       fs.readFile(installVersionFile, 'ascii', function (err, ver) {
   96         if (err && err.code != 'ENOENT') {
   97           return cb(err)
   98         }
   99         var installVersion = parseInt(ver, 10) || 0
  100         log.verbose('got "installVersion"', installVersion)
  101         log.verbose('needs "installVersion"', gyp.package.installVersion)
  102         if (installVersion < gyp.package.installVersion) {
  103           log.verbose('install', 'version is no good; reinstalling')
  104           go()
  105         } else {
  106           log.verbose('install', 'version is good')
  107           cb()
  108         }
  109       })
  110     })
  111   } else {
  112     go()
  113   }
  114 
  115   function getContentSha(res, callback) {
  116     var shasum = crypto.createHash('sha256')
  117     res.on('data', function (chunk) {
  118       shasum.update(chunk)
  119     }).on('end', function () {
  120       callback(null, shasum.digest('hex'))
  121     })
  122   }
  123 
  124   function go () {
  125 
  126     log.verbose('ensuring nodedir is created', devDir)
  127 
  128     // first create the dir for the node dev files
  129     mkdir(devDir, function (err, created) {
  130       if (err) {
  131         if (err.code == 'EACCES') {
  132           eaccesFallback()
  133         } else {
  134           cb(err)
  135         }
  136         return
  137       }
  138 
  139       if (created) {
  140         log.verbose('created nodedir', created)
  141       }
  142 
  143       // now download the node tarball
  144       var tarPath = gyp.opts.tarball
  145       var badDownload = false
  146         , extractCount = 0
  147         , gunzip = zlib.createGunzip()
  148         , extracter = tar.Extract({ path: devDir, strip: 1, filter: isValid })
  149 
  150       var contentShasums = {}
  151       var expectShasums = {}
  152 
  153       // checks if a file to be extracted from the tarball is valid.
  154       // only .h header files and the gyp files get extracted
  155       function isValid () {
  156         var name = this.path.substring(devDir.length + 1)
  157         var isValid = valid(name)
  158         if (name === '' && this.type === 'Directory') {
  159           // the first directory entry is ok
  160           return true
  161         }
  162         if (isValid) {
  163           log.verbose('extracted file from tarball', name)
  164           extractCount++
  165         } else {
  166           // invalid
  167           log.silly('ignoring from tarball', name)
  168         }
  169         return isValid
  170       }
  171 
  172       gunzip.on('error', cb)
  173       extracter.on('error', cb)
  174       extracter.on('end', afterTarball)
  175 
  176       // download the tarball, gunzip and extract!
  177 
  178       if (tarPath) {
  179         var input = fs.createReadStream(tarPath)
  180         input.pipe(gunzip).pipe(extracter)
  181         return
  182       }
  183 
  184       try {
  185         var req = download(gyp, process.env, release.tarballUrl)
  186       } catch (e) {
  187         return cb(e)
  188       }
  189 
  190       // something went wrong downloading the tarball?
  191       req.on('error', function (err) {
  192         if (err.code === 'ENOTFOUND') {
  193           return cb(new Error('This is most likely not a problem with node-gyp or the package itself and\n' +
  194             'is related to network connectivity. In most cases you are behind a proxy or have bad \n' +
  195             'network settings.'))
  196         }
  197         badDownload = true
  198         cb(err)
  199       })
  200 
  201       req.on('close', function () {
  202         if (extractCount === 0) {
  203           cb(new Error('Connection closed while downloading tarball file'))
  204         }
  205       })
  206 
  207       req.on('response', function (res) {
  208         if (res.statusCode !== 200) {
  209           badDownload = true
  210           cb(new Error(res.statusCode + ' response downloading ' + release.tarballUrl))
  211           return
  212         }
  213         // content checksum
  214         getContentSha(res, function (_, checksum) {
  215           var filename = path.basename(release.tarballUrl).trim()
  216           contentShasums[filename] = checksum
  217           log.verbose('content checksum', filename, checksum)
  218         })
  219 
  220         // start unzipping and untaring
  221         req.pipe(gunzip).pipe(extracter)
  222       })
  223 
  224       // invoked after the tarball has finished being extracted
  225       function afterTarball () {
  226         if (badDownload) return
  227         if (extractCount === 0) {
  228           return cb(new Error('There was a fatal problem while downloading/extracting the tarball'))
  229         }
  230         log.verbose('tarball', 'done parsing tarball')
  231         var async = 0
  232 
  233         if (win) {
  234           // need to download node.lib
  235           async++
  236           downloadNodeLib(deref)
  237         }
  238 
  239         // write the "installVersion" file
  240         async++
  241         var installVersionPath = path.resolve(devDir, 'installVersion')
  242         fs.writeFile(installVersionPath, gyp.package.installVersion + '\n', deref)
  243 
  244         // Only download SHASUMS.txt if not using tarPath override
  245         if (!tarPath) {
  246           // download SHASUMS.txt
  247           async++
  248           downloadShasums(deref)
  249         }
  250 
  251         if (async === 0) {
  252           // no async tasks required
  253           cb()
  254         }
  255 
  256         function deref (err) {
  257           if (err) return cb(err)
  258 
  259           async--
  260           if (!async) {
  261             log.verbose('download contents checksum', JSON.stringify(contentShasums))
  262             // check content shasums
  263             for (var k in contentShasums) {
  264               log.verbose('validating download checksum for ' + k, '(%s == %s)', contentShasums[k], expectShasums[k])
  265               if (contentShasums[k] !== expectShasums[k]) {
  266                 cb(new Error(k + ' local checksum ' + contentShasums[k] + ' not match remote ' + expectShasums[k]))
  267                 return
  268               }
  269             }
  270             cb()
  271           }
  272         }
  273       }
  274 
  275       function downloadShasums(done) {
  276         log.verbose('check download content checksum, need to download `SHASUMS256.txt`...')
  277         var shasumsPath = path.resolve(devDir, 'SHASUMS256.txt')
  278 
  279         log.verbose('checksum url', release.shasumsUrl)
  280         try {
  281           var req = download(gyp, process.env, release.shasumsUrl)
  282         } catch (e) {
  283           return cb(e)
  284         }
  285 
  286         req.on('error', done)
  287         req.on('response', function (res) {
  288           if (res.statusCode !== 200) {
  289             done(new Error(res.statusCode + ' status code downloading checksum'))
  290             return
  291           }
  292 
  293           var chunks = []
  294           res.on('data', function (chunk) {
  295             chunks.push(chunk)
  296           })
  297           res.on('end', function () {
  298             var lines = Buffer.concat(chunks).toString().trim().split('\n')
  299             lines.forEach(function (line) {
  300               var items = line.trim().split(/\s+/)
  301               if (items.length !== 2) return
  302 
  303               // 0035d18e2dcf9aad669b1c7c07319e17abfe3762  ./node-v0.11.4.tar.gz
  304               var name = items[1].replace(/^\.\//, '')
  305               expectShasums[name] = items[0]
  306             })
  307 
  308             log.verbose('checksum data', JSON.stringify(expectShasums))
  309             done()
  310           })
  311         })
  312       }
  313 
  314       function downloadNodeLib (done) {
  315         log.verbose('on Windows; need to download `' + release.name + '.lib`...')
  316         var dir32 = path.resolve(devDir, 'ia32')
  317           , dir64 = path.resolve(devDir, 'x64')
  318           , libPath32 = path.resolve(dir32, release.name + '.lib')
  319           , libPath64 = path.resolve(dir64, release.name + '.lib')
  320 
  321         log.verbose('32-bit ' + release.name + '.lib dir', dir32)
  322         log.verbose('64-bit ' + release.name + '.lib dir', dir64)
  323         log.verbose('`' + release.name + '.lib` 32-bit url', release.libUrl32)
  324         log.verbose('`' + release.name + '.lib` 64-bit url', release.libUrl64)
  325 
  326         var async = 2
  327         mkdir(dir32, function (err) {
  328           if (err) return done(err)
  329           log.verbose('streaming 32-bit ' + release.name + '.lib to:', libPath32)
  330 
  331           try {
  332             var req = download(gyp, process.env, release.libUrl32, cb)
  333           } catch (e) {
  334             return cb(e)
  335           }
  336 
  337           req.on('error', done)
  338           req.on('response', function (res) {
  339             if (res.statusCode !== 200) {
  340               done(new Error(res.statusCode + ' status code downloading 32-bit ' + release.name + '.lib'))
  341               return
  342             }
  343 
  344             getContentSha(res, function (_, checksum) {
  345               contentShasums[release.libPath32] = checksum
  346               log.verbose('content checksum', release.libPath32, checksum)
  347             })
  348 
  349             var ws = fs.createWriteStream(libPath32)
  350             ws.on('error', cb)
  351             req.pipe(ws)
  352           })
  353           req.on('end', function () {
  354             --async || done()
  355           })
  356         })
  357         mkdir(dir64, function (err) {
  358           if (err) return done(err)
  359           log.verbose('streaming 64-bit ' + release.name + '.lib to:', libPath64)
  360 
  361           try {
  362             var req = download(gyp, process.env, release.libUrl64, cb)
  363           } catch (e) {
  364             return cb(e)
  365           }
  366 
  367           req.on('error', done)
  368           req.on('response', function (res) {
  369             if (res.statusCode !== 200) {
  370               done(new Error(res.statusCode + ' status code downloading 64-bit ' + release.name + '.lib'))
  371               return
  372             }
  373 
  374             getContentSha(res, function (_, checksum) {
  375               contentShasums[release.libPath64] = checksum
  376               log.verbose('content checksum', release.libPath64, checksum)
  377             })
  378 
  379             var ws = fs.createWriteStream(libPath64)
  380             ws.on('error', cb)
  381             req.pipe(ws)
  382           })
  383           req.on('end', function () {
  384             --async || done()
  385           })
  386         })
  387       } // downloadNodeLib()
  388 
  389     }) // mkdir()
  390 
  391   } // go()
  392 
  393   /**
  394    * Checks if a given filename is "valid" for this installation.
  395    */
  396 
  397   function valid (file) {
  398     // header files
  399     return minimatch(file, '*.h', { matchBase: true }) ||
  400            minimatch(file, '*.gypi', { matchBase: true })
  401   }
  402 
  403   /**
  404    * The EACCES fallback is a workaround for npm's `sudo` behavior, where
  405    * it drops the permissions before invoking any child processes (like
  406    * node-gyp). So what happens is the "nobody" user doesn't have
  407    * permission to create the dev dir. As a fallback, make the tmpdir() be
  408    * the dev dir for this installation. This is not ideal, but at least
  409    * the compilation will succeed...
  410    */
  411 
  412   function eaccesFallback () {
  413     var tmpdir = osenv.tmpdir()
  414     gyp.devDir = path.resolve(tmpdir, '.node-gyp')
  415     log.warn('EACCES', 'user "%s" does not have permission to access the dev dir "%s"', osenv.user(), devDir)
  416     log.warn('EACCES', 'attempting to reinstall using temporary dev dir "%s"', gyp.devDir)
  417     if (process.cwd() == tmpdir) {
  418       log.verbose('tmpdir == cwd', 'automatically will remove dev files after to save disk space')
  419       gyp.todo.push({ name: 'remove', args: argv })
  420     }
  421     gyp.commands.install(argv, cb)
  422   }
  423 
  424 }
  425 
  426 function download (gyp, env, url) {
  427   log.http('GET', url)
  428 
  429   var requestOpts = {
  430       uri: url
  431     , headers: {
  432         'User-Agent': 'node-gyp v' + gyp.version + ' (node ' + process.version + ')'
  433       }
  434   }
  435 
  436   var cafile = gyp.opts.cafile
  437   if (cafile) {
  438     requestOpts.ca = readCAFile(cafile)
  439   }
  440 
  441   // basic support for a proxy server
  442   var proxyUrl = gyp.opts.proxy
  443               || env.http_proxy
  444               || env.HTTP_PROXY
  445               || env.npm_config_proxy
  446   if (proxyUrl) {
  447     if (/^https?:\/\//i.test(proxyUrl)) {
  448       log.verbose('download', 'using proxy url: "%s"', proxyUrl)
  449       requestOpts.proxy = proxyUrl
  450     } else {
  451       log.warn('download', 'ignoring invalid "proxy" config setting: "%s"', proxyUrl)
  452     }
  453   }
  454 
  455   var req = request(requestOpts)
  456   req.on('response', function (res) {
  457     log.http(res.statusCode, url)
  458   })
  459 
  460   return req
  461 }
  462 
  463 function readCAFile (filename) {
  464   // The CA file can contain multiple certificates so split on certificate
  465   // boundaries.  [\S\s]*? is used to match everything including newlines.
  466   var ca = fs.readFileSync(filename, 'utf8')
  467   var re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g
  468   return ca.match(re)
  469 }