build.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. module.exports = exports = build
  2. /**
  3. * Module dependencies.
  4. */
  5. var fs = require('graceful-fs')
  6. , rm = require('rimraf')
  7. , path = require('path')
  8. , glob = require('glob')
  9. , log = require('npmlog')
  10. , which = require('which')
  11. , mkdirp = require('mkdirp')
  12. , exec = require('child_process').exec
  13. , processRelease = require('./process-release')
  14. , win = process.platform == 'win32'
  15. exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module'
  16. function build (gyp, argv, callback) {
  17. var platformMake = 'make'
  18. if (process.platform === 'aix') {
  19. platformMake = 'gmake'
  20. } else if (process.platform.indexOf('bsd') !== -1) {
  21. platformMake = 'gmake'
  22. }
  23. var release = processRelease(argv, gyp, process.version, process.release)
  24. , makeCommand = gyp.opts.make || process.env.MAKE || platformMake
  25. , command = win ? 'msbuild' : makeCommand
  26. , buildDir = path.resolve('build')
  27. , configPath = path.resolve(buildDir, 'config.gypi')
  28. , jobs = gyp.opts.jobs || process.env.JOBS
  29. , buildType
  30. , config
  31. , arch
  32. , nodeDir
  33. , copyDevLib
  34. loadConfigGypi()
  35. /**
  36. * Load the "config.gypi" file that was generated during "configure".
  37. */
  38. function loadConfigGypi () {
  39. fs.readFile(configPath, 'utf8', function (err, data) {
  40. if (err) {
  41. if (err.code == 'ENOENT') {
  42. callback(new Error('You must run `node-gyp configure` first!'))
  43. } else {
  44. callback(err)
  45. }
  46. return
  47. }
  48. config = JSON.parse(data.replace(/\#.+\n/, ''))
  49. // get the 'arch', 'buildType', and 'nodeDir' vars from the config
  50. buildType = config.target_defaults.default_configuration
  51. arch = config.variables.target_arch
  52. nodeDir = config.variables.nodedir
  53. copyDevLib = config.variables.copy_dev_lib == 'true'
  54. if ('debug' in gyp.opts) {
  55. buildType = gyp.opts.debug ? 'Debug' : 'Release'
  56. }
  57. if (!buildType) {
  58. buildType = 'Release'
  59. }
  60. log.verbose('build type', buildType)
  61. log.verbose('architecture', arch)
  62. log.verbose('node dev dir', nodeDir)
  63. if (win) {
  64. findSolutionFile()
  65. } else {
  66. doWhich()
  67. }
  68. })
  69. }
  70. /**
  71. * On Windows, find the first build/*.sln file.
  72. */
  73. function findSolutionFile () {
  74. glob('build/*.sln', function (err, files) {
  75. if (err) return callback(err)
  76. if (files.length === 0) {
  77. return callback(new Error('Could not find *.sln file. Did you run "configure"?'))
  78. }
  79. guessedSolution = files[0]
  80. log.verbose('found first Solution file', guessedSolution)
  81. doWhich()
  82. })
  83. }
  84. /**
  85. * Uses node-which to locate the msbuild / make executable.
  86. */
  87. function doWhich () {
  88. // First make sure we have the build command in the PATH
  89. which(command, function (err, execPath) {
  90. if (err) {
  91. if (win && /not found/.test(err.message)) {
  92. // On windows and no 'msbuild' found. Let's guess where it is
  93. findMsbuild()
  94. } else {
  95. // Some other error or 'make' not found on Unix, report that to the user
  96. callback(err)
  97. }
  98. return
  99. }
  100. log.verbose('`which` succeeded for `' + command + '`', execPath)
  101. copyNodeLib()
  102. })
  103. }
  104. /**
  105. * Search for the location of "msbuild.exe" file on Windows.
  106. */
  107. function findMsbuild () {
  108. log.verbose('could not find "msbuild.exe" in PATH - finding location in registry')
  109. var notfoundErr = 'Can\'t find "msbuild.exe". Do you have Microsoft Visual Studio C++ 2008+ installed?'
  110. var cmd = 'reg query "HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions" /s'
  111. if (process.arch !== 'ia32')
  112. cmd += ' /reg:32'
  113. exec(cmd, function (err, stdout, stderr) {
  114. if (err) {
  115. return callback(new Error(err.message + '\n' + notfoundErr))
  116. }
  117. var reVers = /ToolsVersions\\([^\\]+)$/i
  118. , rePath = /\r\n[ \t]+MSBuildToolsPath[ \t]+REG_SZ[ \t]+([^\r]+)/i
  119. , msbuilds = []
  120. , r
  121. , msbuildPath
  122. stdout.split('\r\n\r\n').forEach(function(l) {
  123. if (!l) return
  124. l = l.trim()
  125. if (r = reVers.exec(l.substring(0, l.indexOf('\r\n')))) {
  126. var ver = parseFloat(r[1], 10)
  127. if (ver >= 3.5) {
  128. if (r = rePath.exec(l)) {
  129. msbuilds.push({
  130. version: ver,
  131. path: r[1]
  132. })
  133. }
  134. }
  135. }
  136. })
  137. msbuilds.sort(function (x, y) {
  138. return (x.version < y.version ? -1 : 1)
  139. })
  140. ;(function verifyMsbuild () {
  141. if (!msbuilds.length) return callback(new Error(notfoundErr))
  142. msbuildPath = path.resolve(msbuilds.pop().path, 'msbuild.exe')
  143. fs.stat(msbuildPath, function (err, stat) {
  144. if (err) {
  145. if (err.code == 'ENOENT') {
  146. if (msbuilds.length) {
  147. return verifyMsbuild()
  148. } else {
  149. callback(new Error(notfoundErr))
  150. }
  151. } else {
  152. callback(err)
  153. }
  154. return
  155. }
  156. command = msbuildPath
  157. copyNodeLib()
  158. })
  159. })()
  160. })
  161. }
  162. /**
  163. * Copies the node.lib file for the current target architecture into the
  164. * current proper dev dir location.
  165. */
  166. function copyNodeLib () {
  167. if (!win || !copyDevLib) return doBuild()
  168. var buildDir = path.resolve(nodeDir, buildType)
  169. , archNodeLibPath = path.resolve(nodeDir, arch, release.name + '.lib')
  170. , buildNodeLibPath = path.resolve(buildDir, release.name + '.lib')
  171. mkdirp(buildDir, function (err, isNew) {
  172. if (err) return callback(err)
  173. log.verbose('"' + buildType + '" dir needed to be created?', isNew)
  174. var rs = fs.createReadStream(archNodeLibPath)
  175. , ws = fs.createWriteStream(buildNodeLibPath)
  176. log.verbose('copying "' + release.name + '.lib" for ' + arch, buildNodeLibPath)
  177. rs.pipe(ws)
  178. rs.on('error', callback)
  179. ws.on('error', callback)
  180. rs.on('end', doBuild)
  181. })
  182. }
  183. /**
  184. * Actually spawn the process and compile the module.
  185. */
  186. function doBuild () {
  187. // Enable Verbose build
  188. var verbose = log.levels[log.level] <= log.levels.verbose
  189. if (!win && verbose) {
  190. argv.push('V=1')
  191. }
  192. if (win && !verbose) {
  193. argv.push('/clp:Verbosity=minimal')
  194. }
  195. if (win) {
  196. // Turn off the Microsoft logo on Windows
  197. argv.push('/nologo')
  198. }
  199. // Specify the build type, Release by default
  200. if (win) {
  201. var p = arch === 'x64' ? 'x64' : 'Win32'
  202. argv.push('/p:Configuration=' + buildType + ';Platform=' + p)
  203. if (jobs) {
  204. var j = parseInt(jobs, 10)
  205. if (!isNaN(j) && j > 0) {
  206. argv.push('/m:' + j)
  207. } else if (jobs.toUpperCase() === 'MAX') {
  208. argv.push('/m:' + require('os').cpus().length)
  209. }
  210. }
  211. } else {
  212. argv.push('BUILDTYPE=' + buildType)
  213. // Invoke the Makefile in the 'build' dir.
  214. argv.push('-C')
  215. argv.push('build')
  216. if (jobs) {
  217. var j = parseInt(jobs, 10)
  218. if (!isNaN(j) && j > 0) {
  219. argv.push('--jobs')
  220. argv.push(j)
  221. } else if (jobs.toUpperCase() === 'MAX') {
  222. argv.push('--jobs')
  223. argv.push(require('os').cpus().length)
  224. }
  225. }
  226. }
  227. if (win) {
  228. // did the user specify their own .sln file?
  229. var hasSln = argv.some(function (arg) {
  230. return path.extname(arg) == '.sln'
  231. })
  232. if (!hasSln) {
  233. argv.unshift(gyp.opts.solution || guessedSolution)
  234. }
  235. }
  236. var proc = gyp.spawn(command, argv)
  237. proc.on('exit', onExit)
  238. }
  239. /**
  240. * Invoked after the make/msbuild command exits.
  241. */
  242. function onExit (code, signal) {
  243. if (code !== 0) {
  244. return callback(new Error('`' + command + '` failed with exit code: ' + code))
  245. }
  246. if (signal) {
  247. return callback(new Error('`' + command + '` got signal: ' + signal))
  248. }
  249. callback()
  250. }
  251. }