Verify platform of vendor binaries at install and run time

This commit is contained in:
Lovell Fuller 2017-09-26 19:06:40 +01:00
parent 57946ed672
commit 7b6c80327e
4 changed files with 131 additions and 67 deletions

View File

@ -10,24 +10,15 @@ const semver = require('semver');
const tar = require('tar'); const tar = require('tar');
const detectLibc = require('detect-libc'); 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 // 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 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 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 // -- Helpers
// Does this file exist?
const isFile = function (file) {
try {
return fs.statSync(file).isFile();
} catch (err) {}
};
const unpack = function (tarPath, done) { const unpack = function (tarPath, done) {
const vendorPath = path.join(__dirname, 'vendor'); const vendorPath = path.join(__dirname, 'vendor');
fs.mkdirSync(vendorPath); fs.mkdirSync(vendorPath);
@ -41,73 +32,68 @@ const unpack = function (tarPath, done) {
.catch(error); .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) { const error = function (msg) {
if (msg instanceof Error) { if (msg instanceof Error) {
msg = msg.message; msg = msg.message;
} }
process.stderr.write('ERROR: ' + msg + '\n'); process.stderr.write(`sharp: ${msg}\n`);
process.exit(1); process.exit(1);
}; };
// -- Binary downloaders // -- Binary downloaders
module.exports.download_vips = function () { module.exports.download_vips = function () {
// Has vips been installed locally? // Check for existing vendored binaries and verify platform matches
const vipsHeaderPath = path.join(__dirname, 'vendor', 'include', 'vips', 'vips.h'); const currentPlatformId = platform();
if (!isFile(vipsHeaderPath)) { try {
// Ensure Intel 64-bit or ARM const vendorPlatformId = require(path.join(__dirname, 'vendor', 'platform.json'));
if (arch === 'ia32') { if (currentPlatformId === vendorPlatformId) {
error('Intel Architecture 32-bit systems require manual installation of libvips - please see http://sharp.dimens.io/page/install'); 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 } catch (err) {}
if (detectLibc.isNonGlibcLinux) { // Ensure Intel 64-bit or ARM
error(`Use with ${detectLibc.family} libc requires manual installation of libvips - please see http://sharp.dimens.io/page/install`); const arch = process.env.npm_config_arch || process.arch;
} if (arch === 'ia32') {
// Ensure glibc >= 2.13 error('Intel Architecture 32-bit systems require manual installation of libvips - please see http://sharp.dimens.io/page/install');
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);
});
} }
// 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 () { module.exports.use_global_vips = function () {

View File

@ -6,8 +6,24 @@ const stream = require('stream');
const events = require('events'); const events = require('events');
const semver = require('semver'); const semver = require('semver');
const is = require('./is'); const is = require('./is');
const platform = require('./platform');
const sharp = require('../build/Release/sharp.node'); 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 // Versioning
let versions = { let versions = {
vips: sharp.libvipsVersion() vips: sharp.libvipsVersion()
@ -21,7 +37,7 @@ let versions = {
} }
// Include versions of dependencies, if present // Include versions of dependencies, if present
try { try {
versions = require('../vendor/lib/versions.json'); versions = require('../vendor/versions.json');
} catch (err) {} } catch (err) {}
})(); })();

15
lib/platform.js Normal file
View File

@ -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('-');
};

47
test/unit/platform.js Normal file
View File

@ -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;
});
});