Skip to content

Commit

Permalink
fix .node loading issue on windows (vercel#1135)
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed Apr 22, 2021
1 parent ee8808a commit 938e0e8
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 31 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
lib-es5
node_modules
dist
110 changes: 79 additions & 31 deletions prelude/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,8 @@ function payloadFileSync(pointer) {
// /////////////////////////////////////////////////////////////////
(() => {
const fs = require('fs');
const path = require('path');

var ancestor = {};
ancestor.dlopen = process.dlopen;

Expand All @@ -1768,55 +1770,101 @@ function payloadFileSync(pointer) {
process.dlopen = function dlopen() {
const args = cloneArgs(arguments);
const modulePath = revertMakingLong(args[1]);
const moduleDirname = require('path').dirname(modulePath);
if (insideSnapshot(modulePath)) {
// Node addon files and .so cannot be read with fs directly, they are loaded with process.dlopen which needs a filesystem path
// we need to write the file somewhere on disk first and then load it
const moduleContent = fs.readFileSync(modulePath);
const moduleBaseName = require('path').basename(modulePath);
const hash = require('crypto')
.createHash('sha256')
.update(moduleContent)
.digest('hex');
const tmpModulePath = `${require('os').tmpdir()}/${hash}_${moduleBaseName}`;
try {
fs.statSync(tmpModulePath);
} catch (e) {
// Most likely this means the module is not on disk yet
fs.writeFileSync(tmpModulePath, moduleContent, { mode: 0o444 });
}
args[1] = tmpModulePath;
}

const moduleDirname = path.dirname(modulePath);
const moduleBaseName = path.basename(modulePath);
const moduleFolder = path.dirname(modulePath);
const unknownModuleErrorRegex = /([^:]+): cannot open shared object file: No such file or directory/;
const tryImporting = function tryImporting(previousErrorMessage) {

function tryImporting(_tmpFolder, previousErrorMessage) {
try {
const res = ancestor.dlopen.apply(process, args);
return res;
} catch (e) {
console.log('cccccc ', e.message);

if (e.message === previousErrorMessage) {
// we already tried to fix this and it didn't work, give up
throw e;
}
if (e.message.match(unknownModuleErrorRegex)) {
// this case triggers on linux, the error message give us a clue on what dynamic linking library
// is missing.
// some modules are packaged with dynamic linking and needs to open other files that should be in
// the same directory, in this case, we write this file in the same /tmp directory and try to
// import the module again

const moduleName = e.message.match(unknownModuleErrorRegex)[1];
const importModulePath = `${moduleDirname}/${moduleName}`;
const moduleContent = fs.readFileSync(importModulePath);
const moduleBaseName = require('path').basename(importModulePath);
const tmpModulePath = `${require('os').tmpdir()}/${moduleBaseName}`;
const importModulePath = path.join(moduleDirname, moduleName);

if (!fs.existsSync(importModulePath)) {
throw new Error(
"INTERNAL ERRROR this files does'nt exist in the virtual file system",
importModulePath
);
}
const moduleContent1 = fs.readFileSync(importModulePath);
const tmpModulePath1 = path.join(_tmpFolder, moduleName);

console.log(' Cannot read :', e.message);

try {
fs.statSync(tmpModulePath);
fs.statSync(tmpModulePath1);
} catch (err) {
fs.writeFileSync(tmpModulePath, moduleContent, { mode: 0o444 });
fs.writeFileSync(tmpModulePath1, moduleContent1, { mode: 0o777 });
}
return tryImporting(_tmpFolder, e.message);
}

// this case triggers on windows mainly.
// we copy all stuff that exists in the folder of the .node module
// into the tempory folders...
const f = fs.readdirSync(moduleFolder);
for (const g of f) {
if (g === moduleBaseName) {
// ignore the current module
continue;
}
const filenameSrc = path.join(moduleFolder, g);
if (fs.statSync(filenameSrc).isSymbolicLink()) {
console.log(' Ignoring symlink file ', filenameSrc);
continue;
}
return tryImporting(e.message);
const filenameDst = path.join(_tmpFolder, g);
const content = fs.readFileSync(filenameSrc);

console.log('cccccc ', filenameSrc, filenameDst);

fs.writeFileSync(filenameDst, content, { mode: 0o777 });
}
throw e;
return tryImporting(_tmpFolder, e.message);
}
};
tryImporting();
}
if (insideSnapshot(modulePath)) {
const moduleContent = fs.readFileSync(modulePath);

// Node addon files and .so cannot be read with fs directly, they are loaded with process.dlopen which needs a filesystem path
// we need to write the file somewhere on disk first and then load it
const hash = require('crypto')
.createHash('sha256')
.update(moduleContent)
.digest('hex');

const _tmpFolder = path.join(require('os').tmpdir(), hash);
if (!fs.existsSync(_tmpFolder)) {
fs.mkdirSync(_tmpFolder);
}
const tmpModulePath = path.join(_tmpFolder, moduleBaseName);

try {
fs.statSync(tmpModulePath);
} catch (e) {
// Most likely this means the module is not on disk yet
fs.writeFileSync(tmpModulePath, moduleContent, { mode: 0o777 });
}
args[1] = tmpModulePath;
tryImporting(_tmpFolder);
} else {
return ancestor.dlopen.apply(process, args);
}
};
})();
2 changes: 2 additions & 0 deletions test/test-1135-issue/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package-lock.json
node_modules
5 changes: 5 additions & 0 deletions test/test-1135-issue/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

/* eslint-disable no-unused-vars */
var canvas = require('canvas');
console.log('42');
61 changes: 61 additions & 0 deletions test/test-1135-issue/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env node

'use strict';

const fs = require('fs');
const path = require('path');
const assert = require('assert');
const utils = require('../utils.js');

assert(__dirname === process.cwd());

const target = process.argv[2] || 'host';
const input = './package.json';
const output = './test-output.' + (process.platform === 'win32' ? '.exe' : '');

console.log('target = ', target);

// remove any possible left-over
utils.vacuum.sync('./node_modules');

const version = utils.exec.sync('node --version');
console.log('node version = ', version);

// launch `npm install`
const npmlog = utils.exec.sync('npm install');
console.log('npm log :', npmlog);

// verify that we have the .pnpm folder and a symlinks module in node_modules
assert(fs.lstatSync(path.join(__dirname, 'node_modules/canvas')).isDirectory());
assert(
fs.lstatSync(path.join(__dirname, 'node_modules/canvas/build')).isDirectory()
);
assert(
fs
.lstatSync(path.join(__dirname, 'node_modules/canvas/build/Release'))
.isDirectory()
);
assert(
fs
.lstatSync(
path.join(__dirname, 'node_modules/canvas/build/Release/canvas.node')
)
.isFile()
);

utils.pkg.sync(['--target', target, '--debug', '--output', output, input]);

// check that produced executable is running and produce the expected output.
const log = utils.spawn.sync(output, [], {
cwd: path.dirname(output),
expect: 0,
});
console.log(log);
// assert(log === '42\n');

// clean up
utils.vacuum.sync(output);
utils.vacuum.sync('./node_modules');
utils.vacuum.sync('./package-lock.json');

console.log('OK');
22 changes: 22 additions & 0 deletions test/test-1135-issue/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "pkg_issue_1135",
"version": "1.0.0",
"description": "",
"main": "index.js",
"bin": "./index.js",
"scripts": {
"pkg": "npx pkg -t host package.json"
},
"pkg": {
"scripts": "index.js",
"outputPath": "dist",
"assets": "./node_modules/canvas/build/Release"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"canvas": "^2.7.0",
"node-pre-gyp": "^0.17.0"
}
}

0 comments on commit 938e0e8

Please sign in to comment.