224 lines
6.6 KiB
JavaScript
224 lines
6.6 KiB
JavaScript
|
var fs = require('fs')
|
||
|
var path = require('path')
|
||
|
var url = require('url')
|
||
|
|
||
|
var vars = (process.config && process.config.variables) || {}
|
||
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY
|
||
|
var versions = process.versions
|
||
|
var abi = versions.modules
|
||
|
if (versions.deno || process.isBun) {
|
||
|
// both Deno and Bun made the very poor decision to shoot themselves in the foot and lie about support for ABI
|
||
|
// (which they do not have)
|
||
|
abi = 'unsupported'
|
||
|
}
|
||
|
var runtime = isElectron() ? 'electron' : 'node'
|
||
|
var arch = process.arch
|
||
|
var platform = process.platform
|
||
|
var libc = process.env.LIBC || (isMusl(platform) ? 'musl' : 'glibc')
|
||
|
var armv = process.env.ARM_VERSION || (arch === 'arm64' ? '8' : vars.arm_version) || ''
|
||
|
var uv = (versions.uv || '').split('.')[0]
|
||
|
|
||
|
module.exports = load
|
||
|
|
||
|
function load (dir) {
|
||
|
// Workaround to fix webpack's build warnings: 'the request of a dependency is an expression', but without
|
||
|
// reassigning require in a way that breaks Bun.
|
||
|
if (typeof __webpack_require__ === 'function')
|
||
|
return __non_webpack_require__(load.path(dir))
|
||
|
else
|
||
|
return require(load.path(dir))
|
||
|
}
|
||
|
|
||
|
load.path = function (dir) {
|
||
|
dir = path.resolve(dir || '.')
|
||
|
var packageName = ''
|
||
|
try {
|
||
|
// explanation above
|
||
|
if (typeof __webpack_require__ === 'function')
|
||
|
packageName = __non_webpack_require__(path.join(dir, 'package.json')).name
|
||
|
else
|
||
|
packageName = require(path.join(dir, 'package.json')).name
|
||
|
var varName = packageName.toUpperCase().replace(/-/g, '_') + '_PREBUILD'
|
||
|
if (process.env[varName]) dir = process.env[varName]
|
||
|
} catch (err) {}
|
||
|
if (!prebuildsOnly) {
|
||
|
var release = getFirst(path.join(dir, 'build/Release'), matchBuild)
|
||
|
if (release) return release
|
||
|
|
||
|
var debug = getFirst(path.join(dir, 'build/Debug'), matchBuild)
|
||
|
if (debug) return debug
|
||
|
}
|
||
|
|
||
|
var prebuild = resolve(dir)
|
||
|
if (prebuild) return prebuild
|
||
|
|
||
|
var nearby = resolve(path.dirname(process.execPath))
|
||
|
if (nearby) return nearby
|
||
|
|
||
|
var platformPackage = (packageName[0] == '@' ? '' : '@' + packageName + '/') + packageName + '-' + platform + '-' + arch
|
||
|
try {
|
||
|
var prebuildPackage = path.dirname(require('module').createRequire(url.pathToFileURL(path.join(dir, 'package.json'))).resolve(platformPackage))
|
||
|
return resolveFile(prebuildPackage)
|
||
|
} catch(error) {}
|
||
|
|
||
|
var target = [
|
||
|
'platform=' + platform,
|
||
|
'arch=' + arch,
|
||
|
'runtime=' + runtime,
|
||
|
'abi=' + abi,
|
||
|
'uv=' + uv,
|
||
|
armv ? 'armv=' + armv : '',
|
||
|
'libc=' + libc,
|
||
|
'node=' + process.versions.node,
|
||
|
process.versions.electron ? 'electron=' + process.versions.electron : '',
|
||
|
typeof __webpack_require__ === 'function' ? 'webpack=true' : '' // eslint-disable-line
|
||
|
].filter(Boolean).join(' ')
|
||
|
|
||
|
throw new Error('No native build was found for ' + target + '\n attempted loading from: ' + dir + ' and package:' +
|
||
|
' ' + platformPackage + '\n')
|
||
|
|
||
|
function resolve (dir) {
|
||
|
// Find matching "prebuilds/<platform>-<arch>" directory
|
||
|
var tuples = readdirSync(path.join(dir, 'prebuilds')).map(parseTuple)
|
||
|
var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0]
|
||
|
if (!tuple) return
|
||
|
return resolveFile(path.join(dir, 'prebuilds', tuple.name))
|
||
|
}
|
||
|
function resolveFile (prebuilds) {
|
||
|
// Find most specific flavor first
|
||
|
var parsed = readdirSync(prebuilds).map(parseTags)
|
||
|
var candidates = parsed.filter(matchTags(runtime, abi))
|
||
|
var winner = candidates.sort(compareTags(runtime))[0]
|
||
|
if (winner) return path.join(prebuilds, winner.file)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function readdirSync (dir) {
|
||
|
try {
|
||
|
return fs.readdirSync(dir)
|
||
|
} catch (err) {
|
||
|
return []
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function getFirst (dir, filter) {
|
||
|
var files = readdirSync(dir).filter(filter)
|
||
|
return files[0] && path.join(dir, files[0])
|
||
|
}
|
||
|
|
||
|
function matchBuild (name) {
|
||
|
return /\.node$/.test(name)
|
||
|
}
|
||
|
|
||
|
function parseTuple (name) {
|
||
|
// Example: darwin-x64+arm64
|
||
|
var arr = name.split('-')
|
||
|
if (arr.length !== 2) return
|
||
|
|
||
|
var platform = arr[0]
|
||
|
var architectures = arr[1].split('+')
|
||
|
|
||
|
if (!platform) return
|
||
|
if (!architectures.length) return
|
||
|
if (!architectures.every(Boolean)) return
|
||
|
|
||
|
return { name, platform, architectures }
|
||
|
}
|
||
|
|
||
|
function matchTuple (platform, arch) {
|
||
|
return function (tuple) {
|
||
|
if (tuple == null) return false
|
||
|
if (tuple.platform !== platform) return false
|
||
|
return tuple.architectures.includes(arch)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function compareTuples (a, b) {
|
||
|
// Prefer single-arch prebuilds over multi-arch
|
||
|
return a.architectures.length - b.architectures.length
|
||
|
}
|
||
|
|
||
|
function parseTags (file) {
|
||
|
var arr = file.split('.')
|
||
|
var extension = arr.pop()
|
||
|
var tags = { file: file, specificity: 0 }
|
||
|
|
||
|
if (extension !== 'node') return
|
||
|
|
||
|
for (var i = 0; i < arr.length; i++) {
|
||
|
var tag = arr[i]
|
||
|
|
||
|
if (tag === 'node' || tag === 'electron' || tag === 'node-webkit') {
|
||
|
tags.runtime = tag
|
||
|
} else if (tag === 'napi') {
|
||
|
tags.napi = true
|
||
|
} else if (tag.slice(0, 3) === 'abi') {
|
||
|
tags.abi = tag.slice(3)
|
||
|
} else if (tag.slice(0, 2) === 'uv') {
|
||
|
tags.uv = tag.slice(2)
|
||
|
} else if (tag.slice(0, 4) === 'armv') {
|
||
|
tags.armv = tag.slice(4)
|
||
|
} else if (tag === 'glibc' || tag === 'musl') {
|
||
|
tags.libc = tag
|
||
|
} else {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
tags.specificity++
|
||
|
}
|
||
|
|
||
|
return tags
|
||
|
}
|
||
|
|
||
|
function matchTags (runtime, abi) {
|
||
|
return function (tags) {
|
||
|
if (tags == null) return false
|
||
|
if (tags.runtime !== runtime && !runtimeAgnostic(tags)) return false
|
||
|
if (tags.abi !== abi && !tags.napi) return false
|
||
|
if (tags.uv && tags.uv !== uv) return false
|
||
|
if (tags.armv && tags.armv !== armv) return false
|
||
|
if (tags.libc && tags.libc !== libc) return false
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function runtimeAgnostic (tags) {
|
||
|
return tags.runtime === 'node' && tags.napi
|
||
|
}
|
||
|
|
||
|
function compareTags (runtime) {
|
||
|
// Precedence: non-agnostic runtime, abi over napi, then by specificity.
|
||
|
return function (a, b) {
|
||
|
if (a.runtime !== b.runtime) {
|
||
|
return a.runtime === runtime ? -1 : 1
|
||
|
} else if (a.abi !== b.abi) {
|
||
|
return a.abi ? -1 : 1
|
||
|
} else if (a.specificity !== b.specificity) {
|
||
|
return a.specificity > b.specificity ? -1 : 1
|
||
|
} else {
|
||
|
return 0
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function isElectron () {
|
||
|
if (process.versions && process.versions.electron) return true
|
||
|
if (process.env.ELECTRON_RUN_AS_NODE) return true
|
||
|
return typeof window !== 'undefined' && window.process && window.process.type === 'renderer'
|
||
|
}
|
||
|
|
||
|
function isMusl (platform) {
|
||
|
if (platform !== 'linux') return false;
|
||
|
const { familySync, MUSL } = require('detect-libc');
|
||
|
return familySync() === MUSL;
|
||
|
}
|
||
|
|
||
|
// Exposed for unit tests
|
||
|
// TODO: move to lib
|
||
|
load.parseTags = parseTags
|
||
|
load.matchTags = matchTags
|
||
|
load.compareTags = compareTags
|
||
|
load.parseTuple = parseTuple
|
||
|
load.matchTuple = matchTuple
|
||
|
load.compareTuples = compareTuples
|