Skip to content
This repository has been archived by the owner on Jan 13, 2024. It is now read-only.

Commit

Permalink
fix .node loading issue on windows (#1335) (#1143)
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed Apr 24, 2021
1 parent a3f9110 commit 3941c7d
Show file tree
Hide file tree
Showing 6 changed files with 169 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
102 changes: 71 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,28 +1770,11 @@ 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 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;
Expand All @@ -1799,24 +1784,79 @@ function payloadFileSync(pointer) {
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(moduleFolder, moduleName);

if (!fs.existsSync(importModulePath)) {
throw new Error(
`INTERNAL ERROR this file doesn't exist in the virtual file system :${importModulePath}`
);
}
const moduleContent1 = fs.readFileSync(importModulePath);
const tmpModulePath1 = path.join(_tmpFolder, moduleName);

try {
fs.statSync(tmpModulePath);
fs.statSync(tmpModulePath1);
} catch (err) {
fs.writeFileSync(tmpModulePath, moduleContent, { mode: 0o444 });
fs.writeFileSync(tmpModulePath1, moduleContent1, { mode: 0o555 });
}
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 files = fs.readdirSync(moduleFolder);
for (const file of files) {
if (file === moduleBaseName) {
// ignore the current module
continue;
}
return tryImporting(e.message);
const filenameSrc = path.join(moduleFolder, file);

if (fs.statSync(filenameSrc).isDirectory()) {
continue;
}
const filenameDst = path.join(_tmpFolder, file);
const content = fs.readFileSync(filenameSrc);

fs.writeFileSync(filenameDst, content, { mode: 0o555 });
}
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: 0o755 });
}
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');
69 changes: 69 additions & 0 deletions test/test-1135-issue/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/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());

/* only run on host */
if (process.argv[2] && process.argv[2] !== 'host') {
console.log('skipped test');
return;
}
const target = /* process.argv[2] || */ 'host';
const input = './package.json';
const output = path.join(
__dirname,
'./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');
21 changes: 21 additions & 0 deletions test/test-1135-issue/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"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"
}
}

0 comments on commit 3941c7d

Please sign in to comment.