1'use strict' 2 3const path = require('path') 4const nopt = require('nopt') 5const log = require('./log') 6const childProcess = require('child_process') 7const { EventEmitter } = require('events') 8 9const commands = [ 10 // Module build commands 11 'build', 12 'clean', 13 'configure', 14 'rebuild', 15 // Development Header File management commands 16 'install', 17 'list', 18 'remove' 19] 20 21class Gyp extends EventEmitter { 22 /** 23 * Export the contents of the package.json. 24 */ 25 package = require('../package.json') 26 27 /** 28 * nopt configuration definitions 29 */ 30 configDefs = { 31 help: Boolean, // everywhere 32 arch: String, // 'configure' 33 cafile: String, // 'install' 34 debug: Boolean, // 'build' 35 directory: String, // bin 36 make: String, // 'build' 37 'msvs-version': String, // 'configure' 38 ensure: Boolean, // 'install' 39 solution: String, // 'build' (windows only) 40 proxy: String, // 'install' 41 noproxy: String, // 'install' 42 devdir: String, // everywhere 43 nodedir: String, // 'configure' 44 loglevel: String, // everywhere 45 python: String, // 'configure' 46 'dist-url': String, // 'install' 47 tarball: String, // 'install' 48 jobs: String, // 'build' 49 thin: String, // 'configure' 50 'force-process-config': Boolean // 'configure' 51 } 52 53 /** 54 * nopt shorthands 55 */ 56 shorthands = { 57 release: '--no-debug', 58 C: '--directory', 59 debug: '--debug', 60 j: '--jobs', 61 silly: '--loglevel=silly', 62 verbose: '--loglevel=verbose', 63 silent: '--loglevel=silent' 64 } 65 66 /** 67 * expose the command aliases for the bin file to use. 68 */ 69 aliases = { 70 ls: 'list', 71 rm: 'remove' 72 } 73 74 constructor (...args) { 75 super(...args) 76 77 this.devDir = '' 78 79 this.commands = commands.reduce((acc, command) => { 80 acc[command] = (argv) => require('./' + command)(this, argv) 81 return acc 82 }, {}) 83 84 Object.defineProperty(this, 'version', { 85 enumerable: true, 86 get: function () { return this.package.version } 87 }) 88 } 89 90 /** 91 * Parses the given argv array and sets the 'opts', 92 * 'argv' and 'command' properties. 93 */ 94 parseArgv (argv) { 95 this.opts = nopt(this.configDefs, this.shorthands, argv) 96 this.argv = this.opts.argv.remain.slice() 97 98 const commands = this.todo = [] 99 100 // create a copy of the argv array with aliases mapped 101 argv = this.argv.map((arg) => { 102 // is this an alias? 103 if (arg in this.aliases) { 104 arg = this.aliases[arg] 105 } 106 return arg 107 }) 108 109 // process the mapped args into "command" objects ("name" and "args" props) 110 argv.slice().forEach((arg) => { 111 if (arg in this.commands) { 112 const args = argv.splice(0, argv.indexOf(arg)) 113 argv.shift() 114 if (commands.length > 0) { 115 commands[commands.length - 1].args = args 116 } 117 commands.push({ name: arg, args: [] }) 118 } 119 }) 120 if (commands.length > 0) { 121 commands[commands.length - 1].args = argv.splice(0) 122 } 123 124 // support for inheriting config env variables from npm 125 const npmConfigPrefix = 'npm_config_' 126 Object.keys(process.env).forEach((name) => { 127 if (name.indexOf(npmConfigPrefix) !== 0) { 128 return 129 } 130 const val = process.env[name] 131 if (name === npmConfigPrefix + 'loglevel') { 132 log.logger.level = val 133 } else { 134 // add the user-defined options to the config 135 name = name.substring(npmConfigPrefix.length) 136 // gyp@741b7f1 enters an infinite loop when it encounters 137 // zero-length options so ensure those don't get through. 138 if (name) { 139 // convert names like force_process_config to force-process-config 140 if (name.includes('_')) { 141 name = name.replace(/_/g, '-') 142 } 143 this.opts[name] = val 144 } 145 } 146 }) 147 148 if (this.opts.loglevel) { 149 log.logger.level = this.opts.loglevel 150 } 151 log.resume() 152 } 153 154 /** 155 * Spawns a child process and emits a 'spawn' event. 156 */ 157 spawn (command, args, opts) { 158 if (!opts) { 159 opts = {} 160 } 161 if (!opts.silent && !opts.stdio) { 162 opts.stdio = [0, 1, 2] 163 } 164 const cp = childProcess.spawn(command, args, opts) 165 log.info('spawn', command) 166 log.info('spawn args', args) 167 return cp 168 } 169 170 /** 171 * Returns the usage instructions for node-gyp. 172 */ 173 usage () { 174 return [ 175 '', 176 ' Usage: node-gyp <command> [options]', 177 '', 178 ' where <command> is one of:', 179 commands.map((c) => ' - ' + c + ' - ' + require('./' + c).usage).join('\n'), 180 '', 181 'node-gyp@' + this.version + ' ' + path.resolve(__dirname, '..'), 182 'node@' + process.versions.node 183 ].join('\n') 184 } 185} 186 187module.exports = () => new Gyp() 188module.exports.Gyp = Gyp 189