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