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

Commit

Permalink
bootstrap: better support of .node files (#1321)
Browse files Browse the repository at this point in the history
When a `.node` file is required `pkg` copies it in a temporary folder like `/tmp/<hash>/addon.node` the problem is that for modules like `sharp` this isn't working as them are statically linked to other `.so` files using relative paths. This pr will copy the entire module folder inside the temporary directory, this allows also to remove the `tryImport` function as there is no reason to try again if that fails. 

I tested this on my end and it's working

I also grouped all hash folders inside `pkg` folder to be sure that the copy will run when users will update pkg, otherwise the copy may not be done when the folder already exists but was created with a previous pkg version

Fixes #1075
  • Loading branch information
robertsLando authored Sep 30, 2021
1 parent 59125d1 commit 165617a
Showing 1 changed file with 66 additions and 70 deletions.
136 changes: 66 additions & 70 deletions prelude/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,49 @@ function createMountpoint(interior, exterior) {
mountpoints.push({ interior, exterior });
}

function copyFileSync(source, target) {
let targetFile = target;

// If target is a directory, a new file with the same name will be created
if (fs.existsSync(target)) {
if (fs.lstatSync(target).isDirectory()) {
targetFile = path.join(target, path.basename(source));
}
}

fs.writeFileSync(targetFile, fs.readFileSync(source));
}

function copyFolderRecursiveSync(source, target) {
let files = [];

// Check if folder needs to be created or integrated
const targetFolder = path.join(target, path.basename(source));
if (!fs.existsSync(targetFolder)) {
fs.mkdirSync(targetFolder);
}

// Copy
if (fs.lstatSync(source).isDirectory()) {
files = fs.readdirSync(source);
files.forEach((file) => {
const curSource = path.join(source, file);
if (fs.lstatSync(curSource).isDirectory()) {
copyFolderRecursiveSync(curSource, targetFolder);
} else {
copyFileSync(curSource, targetFolder);
}
});
}
}

function createDirRecursively(dir) {
if (!fs.existsSync(dir)) {
createDirRecursively(path.join(dir, '..'));
fs.mkdirSync(dir);
}
}

/*
// TODO move to some test
Expand Down Expand Up @@ -2020,88 +2063,41 @@ function payloadFileSync(pointer) {
const modulePath = revertMakingLong(args[1]);
const moduleBaseName = path.basename(modulePath);
const moduleFolder = path.dirname(modulePath);
const unknownModuleErrorRegex = /([^:]+): cannot open shared object file: No such file or directory/;

function tryImporting(_tmpFolder, previousErrorMessage) {
try {
const res = ancestor.dlopen.apply(process, args);
return res;
} catch (e) {
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 = 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(tmpModulePath1);
} catch (err) {
fs.writeFileSync(tmpModulePath1, moduleContent1, { mode: 0o555 });
}
return tryImporting(_tmpFolder, e.message);
}
// Example: moduleFolder = /snapshot/appname/node_modules/sharp/build/Release
const modulePkgPathRegex = /.*?node_modules\/((.+?)\/.*)/;
// Example: modulePackagePath = sharp/build/Release
const modulePackagePath = moduleFolder.match(modulePkgPathRegex)[1];
// Example: modulePackageName = sharp
const modulePackageName = moduleFolder.match(modulePkgPathRegex)[2];
// Example: modulePkgFolder = /snapshot/appname/node_modules/sharp
const modulePkgFolder = moduleFolder.replace(
modulePackagePath,
modulePackageName
);

// this case triggers on windows mainly.
// we copy all stuff that exists in the folder of the .node module
// into the temporary folders...
const files = fs.readdirSync(moduleFolder);
for (const file of files) {
if (file === moduleBaseName) {
// ignore the current module
continue;
}
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 });
}
return tryImporting(_tmpFolder, e.message);
}
}
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
// the hash is needed to be sure we reload the module in case it changes
const hash = createHash('sha256').update(moduleContent).digest('hex');

const tmpFolder = path.join(tmpdir(), hash);
// Example: /tmp/pkg/<hash>
const tmpFolder = path.join(tmpdir(), 'pkg', hash);
if (!fs.existsSync(tmpFolder)) {
fs.mkdirSync(tmpFolder);
// here we copy all files from the snapshot module folder to temporary folder
// we keep the module folder structure to prevent issues with modules that are statically
// linked using relative paths (Fix #1075)
createDirRecursively(tmpFolder);
copyFolderRecursiveSync(modulePkgFolder, 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);
// replace the path with the new module path
args[1] = path.join(tmpFolder, modulePackagePath, moduleBaseName);
}

return ancestor.dlopen.apply(process, args);
};
})();

1 comment on commit 165617a

@Bosskurr991
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow

Please sign in to comment.