create-config-gypi.js 4.7 KB

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