summaryrefslogtreecommitdiff
path: root/deps/npm/node_modules/node-gyp/lib/configure.js
blob: 357f27d7f529260e85153ac75263acac9cab42d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431

module.exports = exports = configure

/**
 * Module dependencies.
 */

var fs = require('graceful-fs')
  , path = require('path')
  , glob = require('glob')
  , log = require('npmlog')
  , osenv = require('osenv')
  , which = require('which')
  , semver = require('semver')
  , mkdirp = require('mkdirp')
  , cp = require('child_process')
  , spawn = cp.spawn
  , execFile = cp.execFile
  , win = process.platform == 'win32'

exports.usage = 'Generates ' + (win ? 'MSVC project files' : 'a Makefile') + ' for the current module'

function configure (gyp, argv, callback) {

  var python = gyp.opts.python || process.env.PYTHON || 'python'
    , buildDir = path.resolve('build')
    , hasVCExpress = false
    , hasVC2012Express = false
    , hasWin71SDK = false
    , hasWin8SDK = false
    , configNames = [ 'config.gypi', 'common.gypi' ]
    , configs = []
    , nodeDir


  if (win) {
    checkVCExpress(function () {
      if (hasVCExpress || hasVC2012Express) {
        checkWinSDK(function () {
          checkPython()
        })
      } else {
        checkPython()
      }
    })
  } else {
    checkPython()
  }

  // Check if Python is in the $PATH
  function checkPython () {
    log.verbose('check python', 'checking for Python executable "%s" in the PATH', python)
    which(python, function (err, execPath) {
      if (err) {
        log.verbose('`which` failed', python, err)
        if (win) {
          guessPython()
        } else {
          failNoPython()
        }
      } else {
        log.verbose('`which` succeeded', python, execPath)
        checkPythonVersion()
      }
    })
  }

  // Called on Windows when "python" isn't available in the current $PATH.
  // We're gonna check if "%SystemDrive%\python27\python.exe" exists.
  function guessPython () {
    log.verbose('could not find "' + python + '". guessing location')
    var rootDir = process.env.SystemDrive || 'C:\\'
    if (rootDir[rootDir.length - 1] !== '\\') {
      rootDir += '\\'
    }
    var pythonPath = path.resolve(rootDir, 'Python27', 'python.exe')
    log.verbose('ensuring that file exists:', pythonPath)
    fs.stat(pythonPath, function (err, stat) {
      if (err) {
        if (err.code == 'ENOENT') {
          failNoPython()
        } else {
          callback(err)
        }
        return
      }
      python = pythonPath
      checkPythonVersion()
    })
  }

  function checkPythonVersion () {
    var env = { TERM: 'dumb', PATH: process.env.PATH };
    execFile(python, ['-c', 'import platform; print(platform.python_version());'], { env: env }, function (err, stdout) {
      if (err) {
        return callback(err)
      }
      log.verbose('check python version', '`%s -c "import platform; print(platform.python_version());"` returned: %j', python, stdout)
      var version = stdout.trim()
      if (~version.indexOf('+')) {
        log.silly('stripping "+" sign(s) from version')
        version = version.replace(/\+/g, '')
      }
      if (~version.indexOf('rc')) {
        log.silly('stripping "rc" identifier from version')
        version = version.replace(/rc(.*)$/ig, '')
      }
      var range = semver.Range('>=2.5.0 <3.0.0');
      if (range.test(version)) {
        getNodeDir()
      } else {
        failPythonVersion(version)
      }
    })
  }

  function failNoPython () {
    callback(new Error('Can\'t find Python executable "' + python +
          '", you can set the PYTHON env variable.'))
  }

  function failPythonVersion (badVersion) {
    callback(new Error('Python executable "' + python +
          '" is v' + badVersion + ', which is not supported by gyp.\n' +
          'You can pass the --python switch to point to Python >= v2.5.0 & < 3.0.0.'))
  }

  function checkWinSDK(cb) {
    checkWin71SDK(function() {
      checkWin8SDK(cb)
    })
  }

  function checkWin71SDK(cb) {
    spawn('reg', ['query', 'HKLM\\Software\\Microsoft\\Microsoft SDKs\\Windows\\v7.1', '/v', 'InstallationFolder'])
         .on('exit', function (code) {
           hasWin71SDK = (code === 0)
           cb()
         })
  }

  function checkWin8SDK(cb) {
    var cp = spawn('reg', ['query', 'HKLM\\Software\\Microsoft\\Windows Kits\\Installed Products', '/f', 'Windows Software Development Kit x86', '/reg:32'])
    cp.on('exit', function (code) {
      hasWin8SDK = (code === 0)
      cb()
    })
  }

  function checkVC2012Express64(cb) {
    var cp = spawn('reg', ['query', 'HKLM\\SOFTWARE\\Wow6432Node\\Microsoft\\VCExpress\\11.0\\Setup\\VC', '/v', 'ProductDir'])
    cp.on('exit', function (code) {
      hasVC2012Express = (code === 0)
      cb()
    })
  }

  function checkVC2012Express(cb) {
    var cp = spawn('reg', ['query', 'HKLM\\SOFTWARE\\Microsoft\\VCExpress\\11.0\\Setup\\VC', '/v', 'ProductDir'])
    cp.on('exit', function (code) {
      hasVC2012Express = (code === 0)
      if (code !== 0) {
        checkVC2012Express64(cb)
      } else {
        cb()
      }
    })
  }

  function checkVCExpress64(cb) {
    var cp = spawn('cmd', ['/C', '%WINDIR%\\SysWOW64\\reg', 'query', 'HKLM\\Software\\Microsoft\\VCExpress\\10.0\\Setup\\VC', '/v', 'ProductDir'])
    cp.on('exit', function (code) {
      hasVCExpress = (code === 0)
      checkVC2012Express(cb)
    })
  }

  function checkVCExpress(cb) {
    spawn('reg', ['query', 'HKLM\\Software\\Microsoft\\VCExpress\\10.0\\Setup\\VC', '/v', 'ProductDir'])
         .on('exit', function (code) {
           if (code !== 0) {
             checkVCExpress64(cb)
           } else {
             checkVC2012Express(cb)
           }
         })
  }

  function getNodeDir () {

    // 'python' should be set by now
    process.env.PYTHON = python

    if (gyp.opts.nodedir) {
      // --nodedir was specified. use that for the dev files
      nodeDir = gyp.opts.nodedir.replace(/^~/, osenv.home())

      log.verbose('get node dir', 'compiling against specified --nodedir dev files: %s', nodeDir)
      createBuildDir()

    } else {
      // if no --nodedir specified, ensure node dependencies are installed
      var version
      var versionStr

      if (gyp.opts.target) {
        // if --target was given, then determine a target version to compile for
        versionStr = gyp.opts.target
        log.verbose('get node dir', 'compiling against --target node version: %s', versionStr)
      } else {
        // if no --target was specified then use the current host node version
        versionStr = process.version
        log.verbose('get node dir', 'no --target version specified, falling back to host node version: %s', versionStr)
      }

      // make sure we have a valid version
      try {
        version = semver.parse(versionStr)
      } catch (e) {
        return callback(e)
      }
      if (!version) {
        return callback(new Error('Invalid version number: ' + versionStr))
      }

      // ensure that the target node version's dev files are installed
      gyp.opts.ensure = true
      gyp.commands.install([ versionStr ], function (err, version) {
        if (err) return callback(err)
        log.verbose('get node dir', 'target node version installed:', version)
        nodeDir = path.resolve(gyp.devDir, version)
        createBuildDir()
      })
    }
  }

  function createBuildDir () {
    log.verbose('build dir', 'attempting to create "build" dir: %s', buildDir)
    mkdirp(buildDir, function (err, isNew) {
      if (err) return callback(err)
      log.verbose('build dir', '"build" dir needed to be created?', isNew)
      createConfigFile()
    })
  }

  function createConfigFile (err) {
    if (err) return callback(err)

    var configFilename = 'config.gypi'
    var configPath = path.resolve(buildDir, configFilename)

    log.verbose('build/' + configFilename, 'creating config file')

    var config = process.config || {}
      , defaults = config.target_defaults
      , variables = config.variables

    // default "config.variables"
    if (!variables) variables = config.variables = {}

    // default "config.defaults"
    if (!defaults) defaults = config.target_defaults = {}

    // don't inherit the "defaults" from node's `process.config` object.
    // doing so could cause problems in cases where the `node` executable was
    // compiled on a different machine (with different lib/include paths) than
    // the machine where the addon is being built to
    defaults.cflags = []
    defaults.defines = []
    defaults.include_dirs = []
    defaults.libraries = []

    // set the default_configuration prop
    if ('debug' in gyp.opts) {
      defaults.default_configuration = gyp.opts.debug ? 'Debug' : 'Release'
    }
    if (!defaults.default_configuration) {
      defaults.default_configuration = 'Release'
    }

    // set the target_arch variable
    variables.target_arch = gyp.opts.arch || process.arch || 'ia32'

    // set the toolset for VCExpress users
    if (win) {
      if (hasVC2012Express && hasWin8SDK) {
        defaults.msbuild_toolset = 'v110'
      } else if (hasVCExpress && hasWin71SDK) {
        defaults.msbuild_toolset = 'Windows7.1SDK'
      }
    }

    // set the node development directory
    variables.nodedir = nodeDir

    // don't copy dev libraries with nodedir option
    variables.copy_dev_lib = !gyp.opts.nodedir

    // disable -T "thin" static archives by default
    variables.standalone_static_library = gyp.opts.thin ? 0 : 1;

    // loop through the rest of the opts and add the unknown ones as variables.
    // this allows for module-specific configure flags like:
    //
    //   $ node-gyp configure --shared-libxml2
    Object.keys(gyp.opts).forEach(function (opt) {
      if (opt === 'argv') return
      if (opt in gyp.configDefs) return
      variables[opt.replace(/-/g, '_')] = gyp.opts[opt]
    })

    // ensures that any boolean values from `process.config` get stringified
    function boolsToString (k, v) {
      if (typeof v === 'boolean')
        return String(v)
      return v
    }

    log.silly('build/' + configFilename, config)

    // now write out the config.gypi file to the build/ dir
    var prefix = '# Do not edit. File was generated by node-gyp\'s "configure" step'
      , json = JSON.stringify(config, boolsToString, 2)
    log.verbose('build/' + configFilename, 'writing out config file: %s', configPath)
    configs.push(configPath)
    fs.writeFile(configPath, [prefix, json, ''].join('\n'), findConfigs)
  }

  function findConfigs (err) {
    if (err) return callback(err)
    var name = configNames.shift()
    if (!name) return runGyp()
    var fullPath = path.resolve(name)
    log.verbose(name, 'checking for gypi file: %s', fullPath)
    fs.stat(fullPath, function (err, stat) {
      if (err) {
        if (err.code == 'ENOENT') {
          findConfigs() // check next gypi filename
        } else {
          callback(err)
        }
      } else {
        log.verbose(name, 'found gypi file')
        configs.push(fullPath)
        findConfigs()
      }
    })
  }

  function runGyp (err) {
    if (err) return callback(err)

    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
        argv.push('-f', 'msvs')
      } else {
        log.verbose('gyp', 'gyp format was not specified; forcing "make"')
        // force the 'make' target for non-Windows
        argv.push('-f', 'make')
      }
    }

    function hasMsvsVersion () {
      return argv.some(function (arg) {
        return arg.indexOf('msvs_version') === 0
      })
    }

    if (win && !hasMsvsVersion()) {
      if ('msvs_version' in gyp.opts) {
        argv.push('-G', 'msvs_version=' + gyp.opts.msvs_version)
      } else {
        argv.push('-G', 'msvs_version=auto')
      }
    }

    // include all the ".gypi" files that were found
    configs.forEach(function (config) {
      argv.push('-I', config)
    })

    // this logic ported from the old `gyp_addon` python file
    var gyp_script = path.resolve(__dirname, '..', 'gyp', 'gyp')
    var addon_gypi = path.resolve(__dirname, '..', 'addon.gypi')
    var common_gypi = path.resolve(nodeDir, 'common.gypi')
    var output_dir = 'build'
    if (win) {
      // Windows expects an absolute path
      output_dir = buildDir
    }

    argv.push('-I', addon_gypi)
    argv.push('-I', common_gypi)
    argv.push('-Dlibrary=shared_library')
    argv.push('-Dvisibility=default')
    argv.push('-Dnode_root_dir=' + nodeDir)
    argv.push('-Dmodule_root_dir=' + process.cwd())
    argv.push('--depth=.')

    // tell gyp to write the Makefile/Solution files into output_dir
    argv.push('--generator-output', output_dir)

    // tell make to write its output into the same dir
    argv.push('-Goutput_dir=.')

    // enforce use of the "binding.gyp" file
    argv.unshift('binding.gyp')

    // execute `gyp` from the current target nodedir
    argv.unshift(gyp_script)

    var cp = gyp.spawn(python, argv)
    cp.on('exit', onCpExit)
  }

  /**
   * Called when the `gyp` child process exits.
   */

  function onCpExit (code, signal) {
    if (code !== 0) {
      callback(new Error('`gyp` failed with exit code: ' + code))
    } else {
      // we're done
      callback()
    }
  }

}