configure.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. 'use strict'
  2. const fs = require('graceful-fs')
  3. const path = require('path')
  4. const log = require('npmlog')
  5. const os = require('os')
  6. const processRelease = require('./process-release')
  7. const win = process.platform === 'win32'
  8. const findNodeDirectory = require('./find-node-directory')
  9. const createConfigGypi = require('./create-config-gypi')
  10. const msgFormat = require('util').format
  11. var findPython = require('./find-python')
  12. if (win) {
  13. var findVisualStudio = require('./find-visualstudio')
  14. }
  15. function configure (gyp, argv, callback) {
  16. var python
  17. var buildDir = path.resolve('build')
  18. var buildBinsDir = path.join(buildDir, 'node_gyp_bins')
  19. var configNames = ['config.gypi', 'common.gypi']
  20. var configs = []
  21. var nodeDir
  22. var release = processRelease(argv, gyp, process.version, process.release)
  23. findPython(gyp.opts.python, function (err, found) {
  24. if (err) {
  25. callback(err)
  26. } else {
  27. python = found
  28. getNodeDir()
  29. }
  30. })
  31. function getNodeDir () {
  32. // 'python' should be set by now
  33. process.env.PYTHON = python
  34. if (gyp.opts.nodedir) {
  35. // --nodedir was specified. use that for the dev files
  36. nodeDir = gyp.opts.nodedir.replace(/^~/, os.homedir())
  37. log.verbose('get node dir', 'compiling against specified --nodedir dev files: %s', nodeDir)
  38. createBuildDir()
  39. } else {
  40. // if no --nodedir specified, ensure node dependencies are installed
  41. if ('v' + release.version !== process.version) {
  42. // if --target was given, then determine a target version to compile for
  43. log.verbose('get node dir', 'compiling against --target node version: %s', release.version)
  44. } else {
  45. // if no --target was specified then use the current host node version
  46. log.verbose('get node dir', 'no --target version specified, falling back to host node version: %s', release.version)
  47. }
  48. if (!release.semver) {
  49. // could not parse the version string with semver
  50. return callback(new Error('Invalid version number: ' + release.version))
  51. }
  52. // If the tarball option is set, always remove and reinstall the headers
  53. // into devdir. Otherwise only install if they're not already there.
  54. gyp.opts.ensure = !gyp.opts.tarball
  55. gyp.commands.install([release.version], function (err) {
  56. if (err) {
  57. return callback(err)
  58. }
  59. log.verbose('get node dir', 'target node version installed:', release.versionDir)
  60. nodeDir = path.resolve(gyp.devDir, release.versionDir)
  61. createBuildDir()
  62. })
  63. }
  64. }
  65. function createBuildDir () {
  66. log.verbose('build dir', 'attempting to create "build" dir: %s', buildDir)
  67. const deepestBuildDirSubdirectory = win ? buildDir : buildBinsDir
  68. fs.mkdir(deepestBuildDirSubdirectory, { recursive: true }, function (err, isNew) {
  69. if (err) {
  70. return callback(err)
  71. }
  72. log.verbose(
  73. 'build dir', '"build" dir needed to be created?', isNew ? 'Yes' : 'No'
  74. )
  75. if (win) {
  76. findVisualStudio(release.semver, gyp.opts.msvs_version,
  77. createConfigFile)
  78. } else {
  79. createPythonSymlink()
  80. createConfigFile()
  81. }
  82. })
  83. }
  84. function createPythonSymlink () {
  85. const symlinkDestination = path.join(buildBinsDir, 'python3')
  86. log.verbose('python symlink', `creating symlink to "${python}" at "${symlinkDestination}"`)
  87. fs.unlink(symlinkDestination, function (err) {
  88. if (err && err.code !== 'ENOENT') {
  89. log.verbose('python symlink', 'error when attempting to remove existing symlink')
  90. log.verbose('python symlink', err.stack, 'errno: ' + err.errno)
  91. }
  92. fs.symlink(python, symlinkDestination, function (err) {
  93. if (err) {
  94. log.verbose('python symlink', 'error when attempting to create Python symlink')
  95. log.verbose('python symlink', err.stack, 'errno: ' + err.errno)
  96. }
  97. })
  98. })
  99. }
  100. function createConfigFile (err, vsInfo) {
  101. if (err) {
  102. return callback(err)
  103. }
  104. if (process.platform === 'win32') {
  105. process.env.GYP_MSVS_VERSION = Math.min(vsInfo.versionYear, 2015)
  106. process.env.GYP_MSVS_OVERRIDE_PATH = vsInfo.path
  107. }
  108. createConfigGypi({ gyp, buildDir, nodeDir, vsInfo }).then(configPath => {
  109. configs.push(configPath)
  110. findConfigs()
  111. }).catch(err => {
  112. callback(err)
  113. })
  114. }
  115. function findConfigs () {
  116. var name = configNames.shift()
  117. if (!name) {
  118. return runGyp()
  119. }
  120. var fullPath = path.resolve(name)
  121. log.verbose(name, 'checking for gypi file: %s', fullPath)
  122. fs.stat(fullPath, function (err) {
  123. if (err) {
  124. if (err.code === 'ENOENT') {
  125. findConfigs() // check next gypi filename
  126. } else {
  127. callback(err)
  128. }
  129. } else {
  130. log.verbose(name, 'found gypi file')
  131. configs.push(fullPath)
  132. findConfigs()
  133. }
  134. })
  135. }
  136. function runGyp (err) {
  137. if (err) {
  138. return callback(err)
  139. }
  140. if (!~argv.indexOf('-f') && !~argv.indexOf('--format')) {
  141. if (win) {
  142. log.verbose('gyp', 'gyp format was not specified; forcing "msvs"')
  143. // force the 'make' target for non-Windows
  144. argv.push('-f', 'msvs')
  145. } else {
  146. log.verbose('gyp', 'gyp format was not specified; forcing "make"')
  147. // force the 'make' target for non-Windows
  148. argv.push('-f', 'make')
  149. }
  150. }
  151. // include all the ".gypi" files that were found
  152. configs.forEach(function (config) {
  153. argv.push('-I', config)
  154. })
  155. // For AIX and z/OS we need to set up the path to the exports file
  156. // which contains the symbols needed for linking.
  157. var nodeExpFile
  158. if (process.platform === 'aix' || process.platform === 'os390' || process.platform === 'os400') {
  159. var ext = process.platform === 'os390' ? 'x' : 'exp'
  160. var nodeRootDir = findNodeDirectory()
  161. var candidates
  162. if (process.platform === 'aix' || process.platform === 'os400') {
  163. candidates = [
  164. 'include/node/node',
  165. 'out/Release/node',
  166. 'out/Debug/node',
  167. 'node'
  168. ].map(function (file) {
  169. return file + '.' + ext
  170. })
  171. } else {
  172. candidates = [
  173. 'out/Release/lib.target/libnode',
  174. 'out/Debug/lib.target/libnode',
  175. 'out/Release/obj.target/libnode',
  176. 'out/Debug/obj.target/libnode',
  177. 'lib/libnode'
  178. ].map(function (file) {
  179. return file + '.' + ext
  180. })
  181. }
  182. var logprefix = 'find exports file'
  183. nodeExpFile = findAccessibleSync(logprefix, nodeRootDir, candidates)
  184. if (nodeExpFile !== undefined) {
  185. log.verbose(logprefix, 'Found exports file: %s', nodeExpFile)
  186. } else {
  187. var msg = msgFormat('Could not find node.%s file in %s', ext, nodeRootDir)
  188. log.error(logprefix, 'Could not find exports file')
  189. return callback(new Error(msg))
  190. }
  191. }
  192. // For z/OS we need to set up the path to zoslib include directory,
  193. // which contains headers included in v8config.h.
  194. var zoslibIncDir
  195. if (process.platform === 'os390') {
  196. logprefix = "find zoslib's zos-base.h:"
  197. let msg
  198. var zoslibIncPath = process.env.ZOSLIB_INCLUDES
  199. if (zoslibIncPath) {
  200. zoslibIncPath = findAccessibleSync(logprefix, zoslibIncPath, ['zos-base.h'])
  201. if (zoslibIncPath === undefined) {
  202. msg = msgFormat('Could not find zos-base.h file in the directory set ' +
  203. 'in ZOSLIB_INCLUDES environment variable: %s; set it ' +
  204. 'to the correct path, or unset it to search %s', process.env.ZOSLIB_INCLUDES, nodeRootDir)
  205. }
  206. } else {
  207. candidates = [
  208. 'include/node/zoslib/zos-base.h',
  209. 'include/zoslib/zos-base.h',
  210. 'zoslib/include/zos-base.h',
  211. 'install/include/node/zoslib/zos-base.h'
  212. ]
  213. zoslibIncPath = findAccessibleSync(logprefix, nodeRootDir, candidates)
  214. if (zoslibIncPath === undefined) {
  215. msg = msgFormat('Could not find any of %s in directory %s; set ' +
  216. 'environmant variable ZOSLIB_INCLUDES to the path ' +
  217. 'that contains zos-base.h', candidates.toString(), nodeRootDir)
  218. }
  219. }
  220. if (zoslibIncPath !== undefined) {
  221. zoslibIncDir = path.dirname(zoslibIncPath)
  222. log.verbose(logprefix, "Found zoslib's zos-base.h in: %s", zoslibIncDir)
  223. } else if (release.version.split('.')[0] >= 16) {
  224. // zoslib is only shipped in Node v16 and above.
  225. log.error(logprefix, msg)
  226. return callback(new Error(msg))
  227. }
  228. }
  229. // this logic ported from the old `gyp_addon` python file
  230. var gypScript = path.resolve(__dirname, '..', 'gyp', 'gyp_main.py')
  231. var addonGypi = path.resolve(__dirname, '..', 'addon.gypi')
  232. var commonGypi = path.resolve(nodeDir, 'include/node/common.gypi')
  233. fs.stat(commonGypi, function (err) {
  234. if (err) {
  235. commonGypi = path.resolve(nodeDir, 'common.gypi')
  236. }
  237. var outputDir = 'build'
  238. if (win) {
  239. // Windows expects an absolute path
  240. outputDir = buildDir
  241. }
  242. var nodeGypDir = path.resolve(__dirname, '..')
  243. var nodeLibFile = path.join(nodeDir,
  244. !gyp.opts.nodedir ? '<(target_arch)' : '$(Configuration)',
  245. release.name + '.lib')
  246. argv.push('-I', addonGypi)
  247. argv.push('-I', commonGypi)
  248. argv.push('-Dlibrary=shared_library')
  249. argv.push('-Dvisibility=default')
  250. argv.push('-Dnode_root_dir=' + nodeDir)
  251. if (process.platform === 'aix' || process.platform === 'os390' || process.platform === 'os400') {
  252. argv.push('-Dnode_exp_file=' + nodeExpFile)
  253. if (process.platform === 'os390' && zoslibIncDir) {
  254. argv.push('-Dzoslib_include_dir=' + zoslibIncDir)
  255. }
  256. }
  257. argv.push('-Dnode_gyp_dir=' + nodeGypDir)
  258. // Do this to keep Cygwin environments happy, else the unescaped '\' gets eaten up,
  259. // resulting in bad paths, Ex c:parentFolderfolderanotherFolder instead of c:\parentFolder\folder\anotherFolder
  260. if (win) {
  261. nodeLibFile = nodeLibFile.replace(/\\/g, '\\\\')
  262. }
  263. argv.push('-Dnode_lib_file=' + nodeLibFile)
  264. argv.push('-Dmodule_root_dir=' + process.cwd())
  265. argv.push('-Dnode_engine=' +
  266. (gyp.opts.node_engine || process.jsEngine || 'v8'))
  267. argv.push('--depth=.')
  268. argv.push('--no-parallel')
  269. // tell gyp to write the Makefile/Solution files into output_dir
  270. argv.push('--generator-output', outputDir)
  271. // tell make to write its output into the same dir
  272. argv.push('-Goutput_dir=.')
  273. // enforce use of the "binding.gyp" file
  274. argv.unshift('binding.gyp')
  275. // execute `gyp` from the current target nodedir
  276. argv.unshift(gypScript)
  277. // make sure python uses files that came with this particular node package
  278. var pypath = [path.join(__dirname, '..', 'gyp', 'pylib')]
  279. if (process.env.PYTHONPATH) {
  280. pypath.push(process.env.PYTHONPATH)
  281. }
  282. process.env.PYTHONPATH = pypath.join(win ? ';' : ':')
  283. var cp = gyp.spawn(python, argv)
  284. cp.on('exit', onCpExit)
  285. })
  286. }
  287. function onCpExit (code) {
  288. if (code !== 0) {
  289. callback(new Error('`gyp` failed with exit code: ' + code))
  290. } else {
  291. // we're done
  292. callback()
  293. }
  294. }
  295. }
  296. /**
  297. * Returns the first file or directory from an array of candidates that is
  298. * readable by the current user, or undefined if none of the candidates are
  299. * readable.
  300. */
  301. function findAccessibleSync (logprefix, dir, candidates) {
  302. for (var next = 0; next < candidates.length; next++) {
  303. var candidate = path.resolve(dir, candidates[next])
  304. try {
  305. var fd = fs.openSync(candidate, 'r')
  306. } catch (e) {
  307. // this candidate was not found or not readable, do nothing
  308. log.silly(logprefix, 'Could not open %s: %s', candidate, e.message)
  309. continue
  310. }
  311. fs.closeSync(fd)
  312. log.silly(logprefix, 'Found readable %s', candidate)
  313. return candidate
  314. }
  315. return undefined
  316. }
  317. module.exports = configure
  318. module.exports.test = {
  319. findAccessibleSync: findAccessibleSync
  320. }
  321. module.exports.usage = 'Generates ' + (win ? 'MSVC project files' : 'a Makefile') + ' for the current module'