diff --git a/binding.gyp b/binding.gyp index 20f94f91..c037e90d 100644 --- a/binding.gyp +++ b/binding.gyp @@ -1,7 +1,8 @@ { 'variables': { 'vips_version': '= 12.13.0. +* Allow multiple platform-arch binaries in same `node_modules` installation tree. + [#2575](https://github.com/lovell/sharp/issues/2575) + ## v0.28 - *bijou* Requires libvips v8.10.6 diff --git a/docs/install.md b/docs/install.md index 804d5ffc..0d9c1816 100644 --- a/docs/install.md +++ b/docs/install.md @@ -206,32 +206,18 @@ to `false` when using the `yarn` package manager. ## AWS Lambda -The binaries in the `node_modules` directory of the +The `node_modules` directory of the [deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html) -must be for the Linux x64 platform. +must include binaries for the Linux x64 platform. When building your deployment package on machines other than Linux x64 (glibc), -run the following commands: +run the following additional command after `npm install`: -macOS: ```sh -rm -rf node_modules/sharp +npm install SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux sharp ``` -Windows: -```sh -rmdir /s /q node_modules/sharp -npm install --arch=x64 --platform=linux sharp -``` - -Alternatively a Docker container closely matching the Lambda runtime can be used: - -```sh -rm -rf node_modules/sharp -docker run -v "$PWD":/var/task lambci/lambda:build-nodejs12.x npm install sharp -``` - To get the best performance select the largest memory available. A 1536 MB function provides ~12x more CPU time than a 128 MB function. diff --git a/install/dll-copy.js b/install/dll-copy.js index 41449ddd..0fc23784 100644 --- a/install/dll-copy.js +++ b/install/dll-copy.js @@ -4,19 +4,19 @@ const fs = require('fs'); const path = require('path'); const libvips = require('../lib/libvips'); +const platform = require('../lib/platform'); const minimumLibvipsVersion = libvips.minimumLibvipsVersion; -const platform = process.env.npm_config_platform || process.platform; -if (platform === 'win32') { - const buildDir = path.join(__dirname, '..', 'build'); - const buildReleaseDir = path.join(buildDir, 'Release'); +const platformAndArch = platform(); + +if (platformAndArch.startsWith('win32')) { + const buildReleaseDir = path.join(__dirname, '..', 'build', 'Release'); libvips.log(`Creating ${buildReleaseDir}`); try { - libvips.mkdirSync(buildDir); libvips.mkdirSync(buildReleaseDir); } catch (err) {} - const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, 'lib'); + const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch, 'lib'); libvips.log(`Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`); try { fs diff --git a/install/libvips.js b/install/libvips.js index e813e8f7..e294fb00 100644 --- a/install/libvips.js +++ b/install/libvips.js @@ -55,9 +55,7 @@ const handleError = function (err) { }; const extractTarball = function (tarPath, platformAndArch) { - const vendorPath = path.join(__dirname, '..', 'vendor'); - libvips.mkdirSync(vendorPath); - const versionedVendorPath = path.join(vendorPath, minimumLibvipsVersion); + const versionedVendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch); libvips.mkdirSync(versionedVendorPath); const ignoreVendorInclude = hasSharpPrebuild.includes(platformAndArch) && !process.env.npm_config_build_from_source; diff --git a/lib/constructor.js b/lib/constructor.js index d25091d2..7a856310 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -5,32 +5,7 @@ const stream = require('stream'); const is = require('./is'); require('./libvips').hasVendoredLibvips(); - -/* istanbul ignore next */ -try { - require('../build/Release/sharp.node'); -} catch (err) { - // Bail early if bindings aren't available - const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, '']; - if (/NODE_MODULE_VERSION/.test(err.message)) { - help.push('- Ensure the version of Node.js used at install time matches that used at runtime'); - } else if (/invalid ELF header/.test(err.message)) { - help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`); - } else if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) { - help.push('- Run "brew update && brew upgrade vips"'); - } else { - help.push( - '- Remove the "node_modules/sharp" directory then run', - ' "npm install --ignore-scripts=false --verbose sharp" and look for errors' - ); - } - help.push( - '- Consult the installation documentation at https://sharp.pixelplumbing.com/install', - '- Search for this error at https://github.com/lovell/sharp/issues', '' - ); - const error = help.join('\n'); - throw new Error(error); -} +require('./sharp'); // Use NODE_DEBUG=sharp to enable libvips warnings const debuglog = util.debuglog('sharp'); diff --git a/lib/input.js b/lib/input.js index f986e999..4c28d6a2 100644 --- a/lib/input.js +++ b/lib/input.js @@ -2,7 +2,7 @@ const color = require('color'); const is = require('./is'); -const sharp = require('../build/Release/sharp.node'); +const sharp = require('./sharp'); /** * Extract input options, if any, from an object. diff --git a/lib/libvips.js b/lib/libvips.js index 4b007ba4..245291e8 100644 --- a/lib/libvips.js +++ b/lib/libvips.js @@ -21,7 +21,7 @@ const spawnSyncOptions = { const mkdirSync = function (dirPath) { try { - fs.mkdirSync(dirPath); + fs.mkdirSync(dirPath, { recursive: true }); } catch (err) { /* istanbul ignore if */ if (err.code !== 'EEXIST') { @@ -67,23 +67,8 @@ const globalLibvipsVersion = function () { }; const hasVendoredLibvips = function () { - const currentPlatformId = platform(); - const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion); - let vendorPlatformId; - try { - vendorPlatformId = require(path.join(vendorPath, 'platform.json')); - } catch (err) {} - /* istanbul ignore else */ - if (vendorPlatformId) { - /* istanbul ignore else */ - if (currentPlatformId === vendorPlatformId) { - return true; - } else { - throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp' directory and run 'npm install' on the '${currentPlatformId}' platform.`); - } - } else { - return false; - } + const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform()); + return fs.existsSync(vendorPath); }; const pkgConfigPath = function () { diff --git a/lib/output.js b/lib/output.js index 98bbcf61..b8be495e 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1,7 +1,7 @@ 'use strict'; const is = require('./is'); -const sharp = require('../build/Release/sharp.node'); +const sharp = require('./sharp'); const formats = new Map([ ['heic', 'heif'], diff --git a/lib/sharp.js b/lib/sharp.js new file mode 100644 index 00000000..95874866 --- /dev/null +++ b/lib/sharp.js @@ -0,0 +1,24 @@ +'use strict'; + +const platformAndArch = require('./platform')(); + +/* istanbul ignore next */ +try { + module.exports = require(`../build/Release/sharp-${platformAndArch}.node`); +} catch (err) { + // Bail early if bindings aren't available + const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, '', 'Possible solutions:']; + if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) { + help.push('- Update Homebrew: "brew update && brew upgrade vips"'); + } else { + help.push( + '- Install with the --verbose flag and look for errors: "npm install --ignore-scripts=false --verbose sharp"', + `- Install for the current runtime: "npm install --platform=${process.platform} --arch=${process.arch} sharp"` + ); + } + help.push( + '- Consult the installation documentation: https://sharp.pixelplumbing.com/install' + ); + console.error(help.join('\n')); + process.exit(1); +} diff --git a/lib/utility.js b/lib/utility.js index 1f14fab5..dc1ad24e 100644 --- a/lib/utility.js +++ b/lib/utility.js @@ -4,7 +4,7 @@ const events = require('events'); const detectLibc = require('detect-libc'); const is = require('./is'); -const sharp = require('../build/Release/sharp.node'); +const sharp = require('./sharp'); /** * An Object containing nested boolean values representing the available input and output formats/methods. diff --git a/test/fixtures/index.js b/test/fixtures/index.js index fac1701c..ec5e35b1 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -2,7 +2,7 @@ const path = require('path'); const sharp = require('../../'); -const maxColourDistance = require('../../build/Release/sharp')._maxColourDistance; +const maxColourDistance = require('../../lib/sharp')._maxColourDistance; // Helpers const getPath = function (filename) {