From 532de4ecab33a4b37848779077de2a98ec3900ec Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Sun, 5 Aug 2018 10:31:41 +0100 Subject: [PATCH] Cache libvips binaries to reduce re-install time #1301 --- docs/changelog.md | 3 ++ install/libvips.js | 68 +++++++++++++++++++++++++------------------- lib/libvips.js | 22 ++++++++++++-- test/unit/libvips.js | 7 +++++ 4 files changed, 67 insertions(+), 33 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index c064ae7f..e8192c08 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -14,6 +14,9 @@ Requires libvips v8.6.1. [#1290](https://github.com/lovell/sharp/pull/1290) [@sylvaindumont](https://github.com/sylvaindumont) +* Cache libvips binaries to reduce re-install time. + [#1301](https://github.com/lovell/sharp/issues/1301) + * Ensure vendor platform mismatch throws error at install time. [#1303](https://github.com/lovell/sharp/issues/1303) diff --git a/install/libvips.js b/install/libvips.js index 0a50f52f..3f9c829e 100644 --- a/install/libvips.js +++ b/install/libvips.js @@ -17,6 +17,22 @@ const platform = require('../lib/platform'); const minimumLibvipsVersion = libvips.minimumLibvipsVersion; const distBaseUrl = process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersion}/`; +const extractTarball = function (tarPath) { + const vendorPath = path.join(__dirname, '..', 'vendor'); + if (!fs.existsSync(vendorPath)) { + fs.mkdirSync(vendorPath); + } + tar + .extract({ + file: tarPath, + cwd: vendorPath, + strict: true + }) + .catch(function (err) { + throw err; + }); +}; + try { const useGlobalLibvips = libvips.useGlobalLibvips(); if (useGlobalLibvips) { @@ -47,37 +63,29 @@ try { } // Download to per-process temporary file const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.gz'; - const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`); - const tmpFile = fs.createWriteStream(tarPathTemp); - const url = distBaseUrl + tarFilename; - npmLog.info('sharp', `Downloading ${url}`); - simpleGet({ url: url, agent: agent() }, function (err, response) { - if (err) { - throw err; - } - if (response.statusCode !== 200) { - throw new Error(`Status ${response.statusCode}`); - } - response.pipe(tmpFile); - }); - tmpFile.on('close', function () { - const vendorPath = path.join(__dirname, '..', 'vendor'); - fs.mkdirSync(vendorPath); - tar - .extract({ - file: tarPathTemp, - cwd: vendorPath, - strict: true - }) - .then(function () { - try { - fs.unlinkSync(tarPathTemp); - } catch (err) {} - }) - .catch(function (err) { + const tarPathCache = path.join(libvips.cachePath(), tarFilename); + if (fs.existsSync(tarPathCache)) { + npmLog.info('sharp', `Using cached ${tarPathCache}`); + extractTarball(tarPathCache); + } else { + const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`); + const tmpFile = fs.createWriteStream(tarPathTemp); + const url = distBaseUrl + tarFilename; + npmLog.info('sharp', `Downloading ${url}`); + simpleGet({ url: url, agent: agent() }, function (err, response) { + if (err) { throw err; - }); - }); + } + if (response.statusCode !== 200) { + throw new Error(`Status ${response.statusCode}`); + } + response.pipe(tmpFile); + }); + tmpFile.on('close', function () { + fs.renameSync(tarPathTemp, tarPathCache); + extractTarball(tarPathCache); + }); + } } } catch (err) { npmLog.error('sharp', err.message); diff --git a/lib/libvips.js b/lib/libvips.js index 53599c95..1a25d497 100644 --- a/lib/libvips.js +++ b/lib/libvips.js @@ -1,17 +1,32 @@ 'use strict'; +const fs = require('fs'); +const os = require('os'); const path = require('path'); const spawnSync = require('child_process').spawnSync; const semver = require('semver'); const platform = require('./platform'); -const minimumLibvipsVersion = process.env.npm_package_config_libvips || require('../package.json').config.libvips; +const env = process.env; +const minimumLibvipsVersion = env.npm_package_config_libvips || require('../package.json').config.libvips; const spawnSyncOptions = { encoding: 'utf8', shell: true }; +const cachePath = function () { + const npmCachePath = env.npm_config_cache || (env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm')); + if (!fs.existsSync(npmCachePath)) { + fs.mkdirSync(npmCachePath); + } + const libvipsCachePath = path.join(npmCachePath, '_libvips'); + if (!fs.existsSync(libvipsCachePath)) { + fs.mkdirSync(libvipsCachePath); + } + return libvipsCachePath; +}; + const globalLibvipsVersion = function () { if (process.platform !== 'win32') { const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout || ''; @@ -40,7 +55,7 @@ const hasVendoredLibvips = function () { const pkgConfigPath = function () { if (process.platform !== 'win32') { const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || ''; - return [brewPkgConfigPath.trim(), process.env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig'] + return [brewPkgConfigPath.trim(), env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig'] .filter(function (p) { return !!p; }) .join(':'); } else { @@ -49,7 +64,7 @@ const pkgConfigPath = function () { }; const useGlobalLibvips = function () { - if (Boolean(process.env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) { + if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) { return false; } @@ -59,6 +74,7 @@ const useGlobalLibvips = function () { module.exports = { minimumLibvipsVersion: minimumLibvipsVersion, + cachePath: cachePath, globalLibvipsVersion: globalLibvipsVersion, hasVendoredLibvips: hasVendoredLibvips, pkgConfigPath: pkgConfigPath, diff --git a/test/unit/libvips.js b/test/unit/libvips.js index f95c7641..195b669f 100644 --- a/test/unit/libvips.js +++ b/test/unit/libvips.js @@ -1,6 +1,7 @@ 'use strict'; const assert = require('assert'); +const fs = require('fs'); const semver = require('semver'); const libvips = require('../../lib/libvips'); @@ -66,5 +67,11 @@ describe('libvips binaries', function () { delete process.env.SHARP_IGNORE_GLOBAL_LIBVIPS; }); + it('cachePath returns a valid path ending with _libvips', function () { + const cachePath = libvips.cachePath(); + assert.strictEqual('string', typeof cachePath); + assert.strictEqual('_libvips', cachePath.substr(-8)); + assert.strictEqual(true, fs.existsSync(cachePath)); + }); }); });