1 |
|
2 |
|
3 |
|
4 | 'use strict';
|
5 |
|
6 | const fs = require('fs');
|
7 | const os = require('os');
|
8 | const path = require('path');
|
9 | const stream = require('stream');
|
10 | const zlib = require('zlib');
|
11 | const { createHash } = require('crypto');
|
12 |
|
13 | const detectLibc = require('detect-libc');
|
14 | const semverCoerce = require('semver/functions/coerce');
|
15 | const semverLessThan = require('semver/functions/lt');
|
16 | const semverSatisfies = require('semver/functions/satisfies');
|
17 | const simpleGet = require('simple-get');
|
18 | const tarFs = require('tar-fs');
|
19 |
|
20 | const agent = require('../lib/agent');
|
21 | const libvips = require('../lib/libvips');
|
22 | const platform = require('../lib/platform');
|
23 |
|
24 | const minimumGlibcVersionByArch = {
|
25 | arm: '2.28',
|
26 | arm64: '2.17',
|
27 | x64: '2.17'
|
28 | };
|
29 |
|
30 | const hasSharpPrebuild = [
|
31 | 'darwin-x64',
|
32 | 'darwin-arm64',
|
33 | 'linux-arm64',
|
34 | 'linux-x64',
|
35 | 'linuxmusl-x64',
|
36 | 'linuxmusl-arm64',
|
37 | 'win32-ia32',
|
38 | 'win32-x64'
|
39 | ];
|
40 |
|
41 | const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
|
42 | const localLibvipsDir = process.env.npm_config_sharp_libvips_local_prebuilds || '';
|
43 | const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
|
44 | const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
|
45 | const installationForced = !!(process.env.npm_config_sharp_install_force || process.env.SHARP_INSTALL_FORCE);
|
46 |
|
47 | const fail = function (err) {
|
48 | libvips.log(err);
|
49 | if (err.code === 'EACCES') {
|
50 | libvips.log('Are you trying to install as a root or sudo user?');
|
51 | libvips.log('- For npm <= v6, try again with the "--unsafe-perm" flag');
|
52 | libvips.log('- For npm >= v8, the user must own the directory "npm install" is run in');
|
53 | }
|
54 | libvips.log('Please see https://sharp.pixelplumbing.com/install for required dependencies');
|
55 | process.exit(1);
|
56 | };
|
57 |
|
58 | const handleError = function (err) {
|
59 | if (installationForced) {
|
60 | libvips.log(`Installation warning: ${err.message}`);
|
61 | } else {
|
62 | throw err;
|
63 | }
|
64 | };
|
65 |
|
66 | const verifyIntegrity = function (platformAndArch) {
|
67 | const expected = libvips.integrity(platformAndArch);
|
68 | if (installationForced || !expected) {
|
69 | libvips.log(`Integrity check skipped for ${platformAndArch}`);
|
70 | return new stream.PassThrough();
|
71 | }
|
72 | const hash = createHash('sha512');
|
73 | return new stream.Transform({
|
74 | transform: function (chunk, _encoding, done) {
|
75 | hash.update(chunk);
|
76 | done(null, chunk);
|
77 | },
|
78 | flush: function (done) {
|
79 | const digest = `sha512-${hash.digest('base64')}`;
|
80 | if (expected !== digest) {
|
81 | try {
|
82 | libvips.removeVendoredLibvips();
|
83 | } catch (err) {
|
84 | libvips.log(err.message);
|
85 | }
|
86 | libvips.log(`Integrity expected: ${expected}`);
|
87 | libvips.log(`Integrity received: ${digest}`);
|
88 | done(new Error(`Integrity check failed for ${platformAndArch}`));
|
89 | } else {
|
90 | libvips.log(`Integrity check passed for ${platformAndArch}`);
|
91 | done();
|
92 | }
|
93 | }
|
94 | });
|
95 | };
|
96 |
|
97 | const extractTarball = function (tarPath, platformAndArch) {
|
98 | const versionedVendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch);
|
99 | libvips.mkdirSync(versionedVendorPath);
|
100 |
|
101 | const ignoreVendorInclude = hasSharpPrebuild.includes(platformAndArch) && !process.env.npm_config_build_from_source;
|
102 | const ignore = function (name) {
|
103 | return ignoreVendorInclude && name.includes('include/');
|
104 | };
|
105 |
|
106 | stream.pipeline(
|
107 | fs.createReadStream(tarPath),
|
108 | verifyIntegrity(platformAndArch),
|
109 | new zlib.BrotliDecompress(),
|
110 | tarFs.extract(versionedVendorPath, { ignore }),
|
111 | function (err) {
|
112 | if (err) {
|
113 | if (/unexpected end of file/.test(err.message)) {
|
114 | fail(new Error(`Please delete ${tarPath} as it is not a valid tarball`));
|
115 | }
|
116 | fail(err);
|
117 | }
|
118 | }
|
119 | );
|
120 | };
|
121 |
|
122 | try {
|
123 | const useGlobalLibvips = libvips.useGlobalLibvips();
|
124 |
|
125 | if (useGlobalLibvips) {
|
126 | const globalLibvipsVersion = libvips.globalLibvipsVersion();
|
127 | libvips.log(`Detected globally-installed libvips v${globalLibvipsVersion}`);
|
128 | libvips.log('Building from source via node-gyp');
|
129 | process.exit(1);
|
130 | } else if (libvips.hasVendoredLibvips()) {
|
131 | libvips.log(`Using existing vendored libvips v${minimumLibvipsVersion}`);
|
132 | } else {
|
133 |
|
134 | const arch = process.env.npm_config_arch || process.arch;
|
135 | const platformAndArch = platform();
|
136 | if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
|
137 | throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
|
138 | }
|
139 | if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
|
140 | throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
|
141 | }
|
142 |
|
143 | const libcVersionRaw = detectLibc.versionSync();
|
144 | if (libcVersionRaw) {
|
145 | const libcFamily = detectLibc.familySync();
|
146 | const libcVersion = semverCoerce(libcVersionRaw).version;
|
147 | if (libcFamily === detectLibc.GLIBC && minimumGlibcVersionByArch[arch]) {
|
148 | if (semverLessThan(libcVersion, semverCoerce(minimumGlibcVersionByArch[arch]).version)) {
|
149 | handleError(new Error(`Use with glibc ${libcVersionRaw} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
|
150 | }
|
151 | }
|
152 | if (libcFamily === detectLibc.MUSL) {
|
153 | if (semverLessThan(libcVersion, '1.1.24')) {
|
154 | handleError(new Error(`Use with musl ${libcVersionRaw} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
|
155 | }
|
156 | }
|
157 | }
|
158 |
|
159 | const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
|
160 | if (!semverSatisfies(process.versions.node, supportedNodeVersion)) {
|
161 | handleError(new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`));
|
162 | }
|
163 |
|
164 | const tarFilename = ['libvips', minimumLibvipsVersionLabelled, platformAndArch].join('-') + '.tar.br';
|
165 | const tarPathCache = path.join(libvips.cachePath(), tarFilename);
|
166 | if (fs.existsSync(tarPathCache)) {
|
167 | libvips.log(`Using cached ${tarPathCache}`);
|
168 | extractTarball(tarPathCache, platformAndArch);
|
169 | } else if (localLibvipsDir) {
|
170 |
|
171 | const tarPathLocal = path.join(path.resolve(localLibvipsDir), `v${minimumLibvipsVersionLabelled}`, tarFilename);
|
172 | libvips.log(`Using local libvips from ${tarPathLocal}`);
|
173 | extractTarball(tarPathLocal, platformAndArch);
|
174 | } else {
|
175 | const url = distBaseUrl + tarFilename;
|
176 | libvips.log(`Downloading ${url}`);
|
177 | simpleGet({ url: url, agent: agent(libvips.log) }, function (err, response) {
|
178 | if (err) {
|
179 | fail(err);
|
180 | } else if (response.statusCode === 404) {
|
181 | fail(new Error(`Prebuilt libvips ${minimumLibvipsVersion} binaries are not yet available for ${platformAndArch}`));
|
182 | } else if (response.statusCode !== 200) {
|
183 | fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
|
184 | } else {
|
185 | const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
|
186 | const tmpFileStream = fs.createWriteStream(tarPathTemp);
|
187 | response
|
188 | .on('error', function (err) {
|
189 | tmpFileStream.destroy(err);
|
190 | })
|
191 | .on('close', function () {
|
192 | if (!response.complete) {
|
193 | tmpFileStream.destroy(new Error('Download incomplete (connection was terminated)'));
|
194 | }
|
195 | })
|
196 | .pipe(tmpFileStream);
|
197 | tmpFileStream
|
198 | .on('error', function (err) {
|
199 |
|
200 | try {
|
201 | fs.unlinkSync(tarPathTemp);
|
202 | } catch (e) {}
|
203 | fail(err);
|
204 | })
|
205 | .on('close', function () {
|
206 | try {
|
207 |
|
208 | fs.renameSync(tarPathTemp, tarPathCache);
|
209 | } catch (err) {
|
210 |
|
211 | fs.copyFileSync(tarPathTemp, tarPathCache);
|
212 | fs.unlinkSync(tarPathTemp);
|
213 | }
|
214 | extractTarball(tarPathCache, platformAndArch);
|
215 | });
|
216 | }
|
217 | });
|
218 | }
|
219 | }
|
220 | } catch (err) {
|
221 | fail(err);
|
222 | }
|