1'use strict' 2 3const fs = require('graceful-fs').promises 4const path = require('path') 5const { glob } = require('glob') 6const log = require('./log') 7const which = require('which') 8const win = process.platform === 'win32' 9 10async function build (gyp, argv) { 11 let platformMake = 'make' 12 if (process.platform === 'aix') { 13 platformMake = 'gmake' 14 } else if (process.platform === 'os400') { 15 platformMake = 'gmake' 16 } else if (process.platform.indexOf('bsd') !== -1) { 17 platformMake = 'gmake' 18 } else if (win && argv.length > 0) { 19 argv = argv.map(function (target) { 20 return '/t:' + target 21 }) 22 } 23 24 const makeCommand = gyp.opts.make || process.env.MAKE || platformMake 25 let command = win ? 'msbuild' : makeCommand 26 const jobs = gyp.opts.jobs || process.env.JOBS 27 let buildType 28 let config 29 let arch 30 let nodeDir 31 let guessedSolution 32 let python 33 let buildBinsDir 34 35 await loadConfigGypi() 36 37 /** 38 * Load the "config.gypi" file that was generated during "configure". 39 */ 40 41 async function loadConfigGypi () { 42 let data 43 try { 44 const configPath = path.resolve('build', 'config.gypi') 45 data = await fs.readFile(configPath, 'utf8') 46 } catch (err) { 47 if (err.code === 'ENOENT') { 48 throw new Error('You must run `node-gyp configure` first!') 49 } else { 50 throw err 51 } 52 } 53 54 config = JSON.parse(data.replace(/#.+\n/, '')) 55 56 // get the 'arch', 'buildType', and 'nodeDir' vars from the config 57 buildType = config.target_defaults.default_configuration 58 arch = config.variables.target_arch 59 nodeDir = config.variables.nodedir 60 python = config.variables.python 61 62 if ('debug' in gyp.opts) { 63 buildType = gyp.opts.debug ? 'Debug' : 'Release' 64 } 65 if (!buildType) { 66 buildType = 'Release' 67 } 68 69 log.verbose('build type', buildType) 70 log.verbose('architecture', arch) 71 log.verbose('node dev dir', nodeDir) 72 log.verbose('python', python) 73 74 if (win) { 75 await findSolutionFile() 76 } else { 77 await doWhich() 78 } 79 } 80 81 /** 82 * On Windows, find the first build/*.sln file. 83 */ 84 85 async function findSolutionFile () { 86 const files = await glob('build/*.sln') 87 if (files.length === 0) { 88 throw new Error('Could not find *.sln file. Did you run "configure"?') 89 } 90 guessedSolution = files[0] 91 log.verbose('found first Solution file', guessedSolution) 92 await doWhich() 93 } 94 95 /** 96 * Uses node-which to locate the msbuild / make executable. 97 */ 98 99 async function doWhich () { 100 // On Windows use msbuild provided by node-gyp configure 101 if (win) { 102 if (!config.variables.msbuild_path) { 103 throw new Error('MSBuild is not set, please run `node-gyp configure`.') 104 } 105 command = config.variables.msbuild_path 106 log.verbose('using MSBuild:', command) 107 await doBuild() 108 return 109 } 110 111 // First make sure we have the build command in the PATH 112 const execPath = await which(command) 113 log.verbose('`which` succeeded for `' + command + '`', execPath) 114 await doBuild() 115 } 116 117 /** 118 * Actually spawn the process and compile the module. 119 */ 120 121 async function doBuild () { 122 // Enable Verbose build 123 const verbose = log.logger.isVisible('verbose') 124 let j 125 126 if (!win && verbose) { 127 argv.push('V=1') 128 } 129 130 if (win && !verbose) { 131 argv.push('/clp:Verbosity=minimal') 132 } 133 134 if (win) { 135 // Turn off the Microsoft logo on Windows 136 argv.push('/nologo') 137 } 138 139 // Specify the build type, Release by default 140 if (win) { 141 // Convert .gypi config target_arch to MSBuild /Platform 142 // Since there are many ways to state '32-bit Intel', default to it. 143 // N.B. msbuild's Condition string equality tests are case-insensitive. 144 const archLower = arch.toLowerCase() 145 const p = archLower === 'x64' 146 ? 'x64' 147 : (archLower === 'arm' 148 ? 'ARM' 149 : (archLower === 'arm64' ? 'ARM64' : 'Win32')) 150 argv.push('/p:Configuration=' + buildType + ';Platform=' + p) 151 if (jobs) { 152 j = parseInt(jobs, 10) 153 if (!isNaN(j) && j > 0) { 154 argv.push('/m:' + j) 155 } else if (jobs.toUpperCase() === 'MAX') { 156 argv.push('/m:' + require('os').cpus().length) 157 } 158 } 159 } else { 160 argv.push('BUILDTYPE=' + buildType) 161 // Invoke the Makefile in the 'build' dir. 162 argv.push('-C') 163 argv.push('build') 164 if (jobs) { 165 j = parseInt(jobs, 10) 166 if (!isNaN(j) && j > 0) { 167 argv.push('--jobs') 168 argv.push(j) 169 } else if (jobs.toUpperCase() === 'MAX') { 170 argv.push('--jobs') 171 argv.push(require('os').cpus().length) 172 } 173 } 174 } 175 176 if (win) { 177 // did the user specify their own .sln file? 178 const hasSln = argv.some(function (arg) { 179 return path.extname(arg) === '.sln' 180 }) 181 if (!hasSln) { 182 argv.unshift(gyp.opts.solution || guessedSolution) 183 } 184 } 185 186 if (!win) { 187 // Add build-time dependency symlinks (such as Python) to PATH 188 buildBinsDir = path.resolve('build', 'node_gyp_bins') 189 process.env.PATH = `${buildBinsDir}:${process.env.PATH}` 190 await fs.mkdir(buildBinsDir, { recursive: true }) 191 const symlinkDestination = path.join(buildBinsDir, 'python3') 192 try { 193 await fs.unlink(symlinkDestination) 194 } catch (err) { 195 if (err.code !== 'ENOENT') throw err 196 } 197 await fs.symlink(python, symlinkDestination) 198 log.verbose('bin symlinks', `created symlink to "${python}" in "${buildBinsDir}" and added to PATH`) 199 } 200 201 const proc = gyp.spawn(command, argv) 202 await new Promise((resolve, reject) => proc.on('exit', async (code, signal) => { 203 if (buildBinsDir) { 204 // Clean up the build-time dependency symlinks: 205 await fs.rm(buildBinsDir, { recursive: true }) 206 } 207 208 if (code !== 0) { 209 return reject(new Error('`' + command + '` failed with exit code: ' + code)) 210 } 211 if (signal) { 212 return reject(new Error('`' + command + '` got signal: ' + signal)) 213 } 214 resolve() 215 })) 216 } 217} 218 219module.exports = build 220module.exports.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module' 221