create-config-gypi.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. 'use strict'
  2. const fs = require('graceful-fs')
  3. const log = require('npmlog')
  4. const path = require('path')
  5. function parseConfigGypi (config) {
  6. // translated from tools/js2c.py of Node.js
  7. // 1. string comments
  8. config = config.replace(/#.*/g, '')
  9. // 2. join multiline strings
  10. config = config.replace(/'$\s+'/mg, '')
  11. // 3. normalize string literals from ' into "
  12. config = config.replace(/'/g, '"')
  13. return JSON.parse(config)
  14. }
  15. async function getBaseConfigGypi ({ gyp, nodeDir }) {
  16. // try reading $nodeDir/include/node/config.gypi first when:
  17. // 1. --dist-url or --nodedir is specified
  18. // 2. and --force-process-config is not specified
  19. const shouldReadConfigGypi = (gyp.opts.nodedir || gyp.opts['dist-url']) && !gyp.opts['force-process-config']
  20. if (shouldReadConfigGypi && nodeDir) {
  21. try {
  22. const baseConfigGypiPath = path.resolve(nodeDir, 'include/node/config.gypi')
  23. const baseConfigGypi = await fs.promises.readFile(baseConfigGypiPath)
  24. return parseConfigGypi(baseConfigGypi.toString())
  25. } catch (err) {
  26. log.warn('read config.gypi', err.message)
  27. }
  28. }
  29. // fallback to process.config if it is invalid
  30. return JSON.parse(JSON.stringify(process.config))
  31. }
  32. async function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo }) {
  33. const config = await getBaseConfigGypi({ gyp, nodeDir })
  34. if (!config.target_defaults) {
  35. config.target_defaults = {}
  36. }
  37. if (!config.variables) {
  38. config.variables = {}
  39. }
  40. const defaults = config.target_defaults
  41. const variables = config.variables
  42. // don't inherit the "defaults" from the base config.gypi.
  43. // doing so could cause problems in cases where the `node` executable was
  44. // compiled on a different machine (with different lib/include paths) than
  45. // the machine where the addon is being built to
  46. defaults.cflags = []
  47. defaults.defines = []
  48. defaults.include_dirs = []
  49. defaults.libraries = []
  50. // set the default_configuration prop
  51. if ('debug' in gyp.opts) {
  52. defaults.default_configuration = gyp.opts.debug ? 'Debug' : 'Release'
  53. }
  54. if (!defaults.default_configuration) {
  55. defaults.default_configuration = 'Release'
  56. }
  57. // set the target_arch variable
  58. variables.target_arch = gyp.opts.arch || process.arch || 'ia32'
  59. if (variables.target_arch === 'arm64') {
  60. defaults.msvs_configuration_platform = 'ARM64'
  61. defaults.xcode_configuration_platform = 'arm64'
  62. }
  63. // set the node development directory
  64. variables.nodedir = nodeDir
  65. // disable -T "thin" static archives by default
  66. variables.standalone_static_library = gyp.opts.thin ? 0 : 1
  67. if (process.platform === 'win32') {
  68. defaults.msbuild_toolset = vsInfo.toolset
  69. if (vsInfo.sdk) {
  70. defaults.msvs_windows_target_platform_version = vsInfo.sdk
  71. }
  72. if (variables.target_arch === 'arm64') {
  73. if (vsInfo.versionMajor > 15 ||
  74. (vsInfo.versionMajor === 15 && vsInfo.versionMajor >= 9)) {
  75. defaults.msvs_enable_marmasm = 1
  76. } else {
  77. log.warn('Compiling ARM64 assembly is only available in\n' +
  78. 'Visual Studio 2017 version 15.9 and above')
  79. }
  80. }
  81. variables.msbuild_path = vsInfo.msBuild
  82. }
  83. // loop through the rest of the opts and add the unknown ones as variables.
  84. // this allows for module-specific configure flags like:
  85. //
  86. // $ node-gyp configure --shared-libxml2
  87. Object.keys(gyp.opts).forEach(function (opt) {
  88. if (opt === 'argv') {
  89. return
  90. }
  91. if (opt in gyp.configDefs) {
  92. return
  93. }
  94. variables[opt.replace(/-/g, '_')] = gyp.opts[opt]
  95. })
  96. return config
  97. }
  98. async function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo }) {
  99. const configFilename = 'config.gypi'
  100. const configPath = path.resolve(buildDir, configFilename)
  101. log.verbose('build/' + configFilename, 'creating config file')
  102. const config = await getCurrentConfigGypi({ gyp, nodeDir, vsInfo })
  103. // ensures that any boolean values in config.gypi get stringified
  104. function boolsToString (k, v) {
  105. if (typeof v === 'boolean') {
  106. return String(v)
  107. }
  108. return v
  109. }
  110. log.silly('build/' + configFilename, config)
  111. // now write out the config.gypi file to the build/ dir
  112. const prefix = '# Do not edit. File was generated by node-gyp\'s "configure" step'
  113. const json = JSON.stringify(config, boolsToString, 2)
  114. log.verbose('build/' + configFilename, 'writing out config file: %s', configPath)
  115. await fs.promises.writeFile(configPath, [prefix, json, ''].join('\n'))
  116. return configPath
  117. }
  118. module.exports = createConfigGypi
  119. module.exports.test = {
  120. parseConfigGypi: parseConfigGypi,
  121. getCurrentConfigGypi: getCurrentConfigGypi
  122. }