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