node-gyp-build.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. var fs = require('fs')
  2. var path = require('path')
  3. var url = require('url')
  4. var os = require('os')
  5. // Workaround to fix webpack's build warnings: 'the request of a dependency is an expression'
  6. var runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require // eslint-disable-line
  7. var vars = (process.config && process.config.variables) || {}
  8. var prebuildsOnly = !!process.env.PREBUILDS_ONLY
  9. var versions = process.versions
  10. var abi = versions.modules
  11. if (versions.deno || process.isBun) {
  12. // both Deno and Bun made the very poor decision to shoot themselves in the foot and lie about support for ABI
  13. // (which they do not have)
  14. abi = 'unsupported'
  15. }
  16. var runtime = isElectron() ? 'electron' : (isNwjs() ? 'node-webkit' : 'node')
  17. var arch = process.env.npm_config_arch || os.arch()
  18. var platform = process.env.npm_config_platform || os.platform()
  19. var libc = process.env.LIBC || (isMusl(platform) ? 'musl' : 'glibc')
  20. var armv = process.env.ARM_VERSION || (arch === 'arm64' ? '8' : vars.arm_version) || ''
  21. var uv = (versions.uv || '').split('.')[0]
  22. module.exports = load
  23. function load (dir) {
  24. return runtimeRequire(load.resolve(dir))
  25. }
  26. load.resolve = load.path = function (dir) {
  27. dir = path.resolve(dir || '.')
  28. var packageName = ''
  29. var packageNameError
  30. try {
  31. packageName = runtimeRequire(path.join(dir, 'package.json')).name;
  32. var varName = packageName.toUpperCase().replace(/-/g, '_')
  33. if (process.env[varName + '_PREBUILD']) dir = process.env[varName + '_PREBUILD']
  34. } catch (err) {
  35. packageNameError = err;
  36. }
  37. if (!prebuildsOnly) {
  38. var release = getFirst(path.join(dir, 'build/Release'), matchBuild)
  39. if (release) return release
  40. var debug = getFirst(path.join(dir, 'build/Debug'), matchBuild)
  41. if (debug) return debug
  42. }
  43. var prebuild = resolve(dir)
  44. if (prebuild) return prebuild
  45. var nearby = resolve(path.dirname(process.execPath))
  46. if (nearby) return nearby
  47. var platformPackage = (packageName[0] == '@' ? '' : '@' + packageName + '/') + packageName + '-' + platform + '-' + arch
  48. var packageResolutionError
  49. try {
  50. var prebuildPackage = path.dirname(require('module').createRequire(url.pathToFileURL(path.join(dir, 'package.json'))).resolve(platformPackage))
  51. return resolveFile(prebuildPackage)
  52. } catch(error) {
  53. packageResolutionError = error
  54. }
  55. var target = [
  56. 'platform=' + platform,
  57. 'arch=' + arch,
  58. 'runtime=' + runtime,
  59. 'abi=' + abi,
  60. 'uv=' + uv,
  61. armv ? 'armv=' + armv : '',
  62. 'libc=' + libc,
  63. 'node=' + process.versions.node,
  64. process.versions.electron ? 'electron=' + process.versions.electron : '',
  65. typeof __webpack_require__ === 'function' ? 'webpack=true' : '' // eslint-disable-line
  66. ].filter(Boolean).join(' ')
  67. let errMessage = 'No native build was found for ' + target + '\n attempted loading from: ' + dir + ' and package:' +
  68. ' ' + platformPackage + '\n';
  69. if (packageNameError) {
  70. errMessage += 'Error finding package.json: ' + packageNameError.message + '\n';
  71. }
  72. if (packageResolutionError) {
  73. errMessage += 'Error resolving package: ' + packageResolutionError.message + '\n';
  74. }
  75. throw new Error(errMessage)
  76. function resolve (dir) {
  77. // Find matching "prebuilds/<platform>-<arch>" directory
  78. var tuples = readdirSync(path.join(dir, 'prebuilds')).map(parseTuple)
  79. var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0]
  80. if (!tuple) return
  81. return resolveFile(path.join(dir, 'prebuilds', tuple.name))
  82. }
  83. function resolveFile (prebuilds) {
  84. // Find most specific flavor first
  85. var parsed = readdirSync(prebuilds).map(parseTags)
  86. var candidates = parsed.filter(matchTags(runtime, abi))
  87. var winner = candidates.sort(compareTags(runtime))[0]
  88. if (winner) return path.join(prebuilds, winner.file)
  89. }
  90. }
  91. function readdirSync (dir) {
  92. try {
  93. return fs.readdirSync(dir)
  94. } catch (err) {
  95. return []
  96. }
  97. }
  98. function getFirst (dir, filter) {
  99. var files = readdirSync(dir).filter(filter)
  100. return files[0] && path.join(dir, files[0])
  101. }
  102. function matchBuild (name) {
  103. return /\.node$/.test(name)
  104. }
  105. function parseTuple (name) {
  106. // Example: darwin-x64+arm64
  107. var arr = name.split('-')
  108. if (arr.length !== 2) return
  109. var platform = arr[0]
  110. var architectures = arr[1].split('+')
  111. if (!platform) return
  112. if (!architectures.length) return
  113. if (!architectures.every(Boolean)) return
  114. return { name, platform, architectures }
  115. }
  116. function matchTuple (platform, arch) {
  117. return function (tuple) {
  118. if (tuple == null) return false
  119. if (tuple.platform !== platform) return false
  120. return tuple.architectures.includes(arch)
  121. }
  122. }
  123. function compareTuples (a, b) {
  124. // Prefer single-arch prebuilds over multi-arch
  125. return a.architectures.length - b.architectures.length
  126. }
  127. function parseTags (file) {
  128. var arr = file.split('.')
  129. var extension = arr.pop()
  130. var tags = { file: file, specificity: 0 }
  131. if (extension !== 'node') return
  132. for (var i = 0; i < arr.length; i++) {
  133. var tag = arr[i]
  134. if (tag === 'node' || tag === 'electron' || tag === 'node-webkit') {
  135. tags.runtime = tag
  136. } else if (tag === 'napi') {
  137. tags.napi = true
  138. } else if (tag.slice(0, 3) === 'abi') {
  139. tags.abi = tag.slice(3)
  140. } else if (tag.slice(0, 2) === 'uv') {
  141. tags.uv = tag.slice(2)
  142. } else if (tag.slice(0, 4) === 'armv') {
  143. tags.armv = tag.slice(4)
  144. } else if (tag === 'glibc' || tag === 'musl') {
  145. tags.libc = tag
  146. } else {
  147. continue
  148. }
  149. tags.specificity++
  150. }
  151. return tags
  152. }
  153. function matchTags (runtime, abi) {
  154. return function (tags) {
  155. if (tags == null) return false
  156. if (tags.runtime !== runtime && !runtimeAgnostic(tags)) return false
  157. if (tags.abi !== abi && !tags.napi) return false
  158. if (tags.uv && tags.uv !== uv) return false
  159. if (tags.armv && tags.armv !== armv) return false
  160. if (tags.libc && tags.libc !== libc) return false
  161. return true
  162. }
  163. }
  164. function runtimeAgnostic (tags) {
  165. return tags.runtime === 'node' && tags.napi
  166. }
  167. function compareTags (runtime) {
  168. // Precedence: non-agnostic runtime, abi over napi, then by specificity.
  169. return function (a, b) {
  170. if (a.runtime !== b.runtime) {
  171. return a.runtime === runtime ? -1 : 1
  172. } else if (a.abi !== b.abi) {
  173. return a.abi ? -1 : 1
  174. } else if (a.specificity !== b.specificity) {
  175. return a.specificity > b.specificity ? -1 : 1
  176. } else {
  177. return 0
  178. }
  179. }
  180. }
  181. function isNwjs () {
  182. return !!(process.versions && process.versions.nw)
  183. }
  184. function isElectron () {
  185. if (process.versions && process.versions.electron) return true
  186. if (process.env.ELECTRON_RUN_AS_NODE) return true
  187. return typeof window !== 'undefined' && window.process && window.process.type === 'renderer'
  188. }
  189. function isMusl (platform) {
  190. if (platform !== 'linux') return false;
  191. const { familySync, MUSL } = require('detect-libc');
  192. return familySync() === MUSL;
  193. }
  194. // Exposed for unit tests
  195. // TODO: move to lib
  196. load.parseTags = parseTags
  197. load.matchTags = matchTags
  198. load.compareTags = compareTags
  199. load.parseTuple = parseTuple
  200. load.matchTuple = matchTuple
  201. load.compareTuples = compareTuples