diff --git a/README.md b/README.md index e83356c..25ed222 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,7 @@ Errors have a `required` and `current` fields. ### .checkEngine(pkg, npmVer, nodeVer, force = false) -Check if node/npm version is supported by the package. If it isn't -supported, an error is thrown. +Check if a package's `engines.node` and `engines.npm` match the running system. `force` argument will override the node version check, but not the npm version check, as this typically would indicate that the current version of @@ -22,6 +21,8 @@ Error code: 'EBADENGINE' ### .checkPlatform(pkg, force) -Check if OS/Arch is supported by the package. +Check if a package's `os`, `cpu` and `libc` match the running system. + +`force` argument skips all checks. Error code: 'EBADPLATFORM' diff --git a/lib/index.js b/lib/index.js index 728e8cf..37530bb 100644 --- a/lib/index.js +++ b/lib/index.js @@ -20,6 +20,8 @@ const checkEngine = (target, npmVer, nodeVer, force = false) => { } } +const isMusl = (file) => file.includes('libc.musl-') || file.includes('ld-musl-') + const checkPlatform = (target, force = false) => { if (force) { return @@ -30,16 +32,35 @@ const checkPlatform = (target, force = false) => { const osOk = target.os ? checkList(platform, target.os) : true const cpuOk = target.cpu ? checkList(arch, target.cpu) : true - if (!osOk || !cpuOk) { + let libcOk = true + let libcFamily = null + if (target.libc) { + // libc checks only work in linux, any value is a failure if we aren't + if (platform !== 'linux') { + libcOk = false + } else { + const report = process.report.getReport() + if (report.header?.glibcRuntimeVersion) { + libcFamily = 'glibc' + } else if (Array.isArray(report.sharedObjects) && report.sharedObjects.some(isMusl)) { + libcFamily = 'musl' + } + libcOk = libcFamily ? checkList(libcFamily, target.libc) : false + } + } + + if (!osOk || !cpuOk || !libcOk) { throw Object.assign(new Error('Unsupported platform'), { pkgid: target._id, current: { os: platform, cpu: arch, + libc: libcFamily, }, required: { os: target.os, cpu: target.cpu, + libc: target.libc, }, code: 'EBADPLATFORM', }) diff --git a/test/check-platform.js b/test/check-platform.js index f6c1068..e27390d 100644 --- a/test/check-platform.js +++ b/test/check-platform.js @@ -42,3 +42,81 @@ t.test('os wrong (negation)', async t => t.test('nothing wrong (negation)', async t => checkPlatform({ cpu: '!enten-cpu', os: '!enten-os' })) + +t.test('libc', (t) => { + let PLATFORM = '' + + const _processPlatform = Object.getOwnPropertyDescriptor(process, 'platform') + Object.defineProperty(process, 'platform', { + enumerable: true, + configurable: true, + get: () => PLATFORM, + }) + + let REPORT = {} + const _processReport = process.report.getReport + process.report.getReport = () => REPORT + + t.teardown(() => { + Object.defineProperty(process, 'platform', _processPlatform) + process.report.getReport = _processReport + }) + + t.test('fails when not in linux', (t) => { + PLATFORM = 'darwin' + + t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, + 'fails for glibc when not in linux') + t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' }, + 'fails for musl when not in linux') + t.end() + }) + + t.test('glibc', (t) => { + PLATFORM = 'linux' + + REPORT = {} + t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, + 'fails when report is missing header property') + + REPORT = { header: {} } + t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, + 'fails when header is missing glibcRuntimeVersion property') + + REPORT = { header: { glibcRuntimeVersion: '1' } } + t.doesNotThrow(() => checkPlatform({ libc: 'glibc' }), 'allows glibc on glibc') + t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' }, + 'does not allow musl on glibc') + + t.end() + }) + + t.test('musl', (t) => { + PLATFORM = 'linux' + + REPORT = {} + t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' }, + 'fails when report is missing sharedObjects property') + + REPORT = { sharedObjects: {} } + t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' }, + 'fails when sharedObjects property is not an array') + + REPORT = { sharedObjects: [] } + t.throws(() => checkPlatform({ libc: 'musl' }), { code: 'EBADPLATFORM' }, + 'fails when sharedObjects does not contain musl') + + REPORT = { sharedObjects: ['ld-musl-foo'] } + t.doesNotThrow(() => checkPlatform({ libc: 'musl' }), 'allows musl on musl as ld-musl-') + + REPORT = { sharedObjects: ['libc.musl-'] } + t.doesNotThrow(() => checkPlatform({ libc: 'musl' }), 'allows musl on musl as libc.musl-') + + t.throws(() => checkPlatform({ libc: 'glibc' }), { code: 'EBADPLATFORM' }, + 'does not allow glibc on musl') + + t.end() + }) + + t.end() +})