diff --git a/binding.js b/binding.js index 0ef7e57b..f600dec5 100644 --- a/binding.js +++ b/binding.js @@ -10,24 +10,15 @@ const semver = require('semver'); const tar = require('tar'); const detectLibc = require('detect-libc'); +const platform = require('./lib/platform'); + // Use NPM-provided environment variable where available, falling back to require-based method for Electron const minimumLibvipsVersion = process.env.npm_package_config_libvips || require('./package.json').config.libvips; const distBaseUrl = process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersion}/`; -const platform = process.env.npm_config_platform || process.platform; - -const arch = process.env.npm_config_arch || process.arch; - // -- Helpers -// Does this file exist? -const isFile = function (file) { - try { - return fs.statSync(file).isFile(); - } catch (err) {} -}; - const unpack = function (tarPath, done) { const vendorPath = path.join(__dirname, 'vendor'); fs.mkdirSync(vendorPath); @@ -41,73 +32,68 @@ const unpack = function (tarPath, done) { .catch(error); }; -const platformId = function () { - const platformId = [platform]; - if (arch === 'arm' || arch === 'armhf' || arch === 'arm64') { - const armVersion = (arch === 'arm64') ? '8' : process.env.npm_config_armv || process.config.variables.arm_version || '6'; - platformId.push('armv' + armVersion); - } else { - platformId.push(arch); - } - return platformId.join('-'); -}; - -// Error const error = function (msg) { if (msg instanceof Error) { msg = msg.message; } - process.stderr.write('ERROR: ' + msg + '\n'); + process.stderr.write(`sharp: ${msg}\n`); process.exit(1); }; // -- Binary downloaders module.exports.download_vips = function () { - // Has vips been installed locally? - const vipsHeaderPath = path.join(__dirname, 'vendor', 'include', 'vips', 'vips.h'); - if (!isFile(vipsHeaderPath)) { - // Ensure Intel 64-bit or ARM - if (arch === 'ia32') { - error('Intel Architecture 32-bit systems require manual installation of libvips - please see http://sharp.dimens.io/page/install'); + // Check for existing vendored binaries and verify platform matches + const currentPlatformId = platform(); + try { + const vendorPlatformId = require(path.join(__dirname, 'vendor', 'platform.json')); + if (currentPlatformId === vendorPlatformId) { + return; + } else { + error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`); } - // Ensure glibc Linux - if (detectLibc.isNonGlibcLinux) { - error(`Use with ${detectLibc.family} libc requires manual installation of libvips - please see http://sharp.dimens.io/page/install`); - } - // Ensure glibc >= 2.13 - if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.13.0')) { - error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips - please see http://sharp.dimens.io/page/install`); - } - // Arch/platform-specific .tar.gz - const tarFilename = ['libvips', minimumLibvipsVersion, platformId()].join('-') + '.tar.gz'; - // Download to per-process temporary file - const tarPathTemp = path.join(os.tmpdir(), process.pid + '-' + tarFilename); - const tmpFile = fs.createWriteStream(tarPathTemp).on('finish', function () { - unpack(tarPathTemp, function () { - // Attempt to remove temporary file - try { - fs.unlinkSync(tarPathTemp); - } catch (err) {} - }); - }); - const url = distBaseUrl + tarFilename; - const simpleGetOpt = { - url: url, - agent: caw(null, { - protocol: 'https' - }) - }; - simpleGet(simpleGetOpt, function (err, response) { - if (err) { - error('Download of ' + url + ' failed: ' + err.message); - } - if (response.statusCode !== 200) { - error(url + ' status code ' + response.statusCode); - } - response.pipe(tmpFile); - }); + } catch (err) {} + // Ensure Intel 64-bit or ARM + const arch = process.env.npm_config_arch || process.arch; + if (arch === 'ia32') { + error('Intel Architecture 32-bit systems require manual installation of libvips - please see http://sharp.dimens.io/page/install'); } + // Ensure glibc Linux + if (detectLibc.isNonGlibcLinux) { + error(`Use with ${detectLibc.family} libc requires manual installation of libvips - please see http://sharp.dimens.io/page/install`); + } + // Ensure glibc >= 2.13 + if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.13.0')) { + error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips - please see http://sharp.dimens.io/page/install`); + } + // Arch/platform-specific .tar.gz + const tarFilename = ['libvips', minimumLibvipsVersion, currentPlatformId].join('-') + '.tar.gz'; + // Download to per-process temporary file + const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`); + const tmpFile = fs.createWriteStream(tarPathTemp).on('finish', function () { + unpack(tarPathTemp, function () { + // Attempt to remove temporary file + try { + fs.unlinkSync(tarPathTemp); + } catch (err) {} + }); + }); + const url = distBaseUrl + tarFilename; + const simpleGetOpt = { + url: url, + agent: caw(null, { + protocol: 'https' + }) + }; + simpleGet(simpleGetOpt, function (err, response) { + if (err) { + error(`${url} download failed: ${err.message}`); + } + if (response.statusCode !== 200) { + error(`${url} download failed: status ${response.statusCode}`); + } + response.pipe(tmpFile); + }); }; module.exports.use_global_vips = function () { diff --git a/lib/constructor.js b/lib/constructor.js index b4364c6a..408ae049 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -6,8 +6,24 @@ const stream = require('stream'); const events = require('events'); const semver = require('semver'); const is = require('./is'); +const platform = require('./platform'); const sharp = require('../build/Release/sharp.node'); +// Vendor platform +(function () { + let vendorPlatformId; + try { + vendorPlatformId = require('../vendor/platform.json'); + } catch (err) { + return; + } + const currentPlatformId = platform(); + /* istanbul ignore if */ + if (currentPlatformId !== vendorPlatformId) { + throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp/vendor' directory and run 'npm rebuild'.`); + } +})(); + // Versioning let versions = { vips: sharp.libvipsVersion() @@ -21,7 +37,7 @@ let versions = { } // Include versions of dependencies, if present try { - versions = require('../vendor/lib/versions.json'); + versions = require('../vendor/versions.json'); } catch (err) {} })(); diff --git a/lib/platform.js b/lib/platform.js new file mode 100644 index 00000000..0d0722ba --- /dev/null +++ b/lib/platform.js @@ -0,0 +1,15 @@ +'use strict'; + +module.exports = function () { + const arch = process.env.npm_config_arch || process.arch; + const platform = process.env.npm_config_platform || process.platform; + + const platformId = [platform]; + if (arch === 'arm' || arch === 'armhf' || arch === 'arm64') { + const armVersion = (arch === 'arm64') ? '8' : process.env.npm_config_armv || process.config.variables.arm_version || '6'; + platformId.push(`armv${armVersion}`); + } else { + platformId.push(arch); + } + return platformId.join('-'); +}; diff --git a/test/unit/platform.js b/test/unit/platform.js new file mode 100644 index 00000000..87454c78 --- /dev/null +++ b/test/unit/platform.js @@ -0,0 +1,47 @@ +'use strict'; + +const assert = require('assert'); +const platform = require('../../lib/platform'); + +describe('Platform-detection', function () { + it('Can override arch with npm_config_arch', function () { + process.env.npm_config_arch = 'test'; + assert.strictEqual('test', platform().split('-')[1]); + delete process.env.npm_config_arch; + }); + + it('Can override platform with npm_config_platform', function () { + process.env.npm_config_platform = 'test'; + assert.strictEqual('test', platform().split('-')[0]); + delete process.env.npm_config_platform; + }); + + it('Can override ARM version via npm_config_armv', function () { + process.env.npm_config_arch = 'arm'; + process.env.npm_config_armv = 'test'; + assert.strictEqual('armvtest', platform().split('-')[1]); + delete process.env.npm_config_armv; + delete process.env.npm_config_arch; + }); + + it('Can detect ARM version via process.config', function () { + process.env.npm_config_arch = 'armhf'; + const armVersion = process.config.variables.arm_version; + process.config.variables.arm_version = 'test'; + assert.strictEqual('armvtest', platform().split('-')[1]); + process.config.variables.arm_version = armVersion; + delete process.env.npm_config_arch; + }); + + it('Defaults to ARMv6 for 32-bit', function () { + process.env.npm_config_arch = 'arm'; + assert.strictEqual('armv6', platform().split('-')[1]); + delete process.env.npm_config_arch; + }); + + it('Defaults to ARMv8 for 64-bit', function () { + process.env.npm_config_arch = 'arm64'; + assert.strictEqual('armv8', platform().split('-')[1]); + delete process.env.npm_config_arch; + }); +});