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

fix .node loading issue on windows (#1335) #1143

Merged
merged 1 commit into from
Apr 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
erossignon marked this conversation as resolved.
Show resolved Hide resolved
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"
}
}