Skip to content

Commit

Permalink
fix(kernel): fast module loading fails on Windows (EPERM) (#4212)
Browse files Browse the repository at this point in the history
In #4181, a faster method to load modules was introduced: symlinking instead of recursing through the directory tree, mostly affecting the load times of large modules.

Since Windows Vista, non-Administrator users on Windows aren't allowed to create symlinks anymore, so this new loading method fails for users working in corporate Windows environments.

Catch the error and fall back to the slower copying method if that happens.

Fixes #4208.



---

By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license].

[Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
  • Loading branch information
rix0rrr committed Aug 8, 2023
1 parent 5ebb43b commit fac6cbb
Showing 1 changed file with 39 additions and 19 deletions.
58 changes: 39 additions & 19 deletions packages/@jsii/kernel/src/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
statSync,
symlinkSync,
} from 'fs';
import * as os from 'os';
import { dirname, join } from 'path';

/**
Expand All @@ -16,31 +17,50 @@ import { dirname, join } from 'path';
const PRESERVE_SYMLINKS = process.execArgv.includes('--preserve-symlinks');

/**
* Creates directories containing hard links if possible, and falls back on
* copy otherwise.
* Link existing to destination directory
*
* @param existing is the original file or directory to link.
* @param destination is the new file or directory to create.
* - If Node has been started with a module resolution strategy that does not
* resolve symlinks (so peerDependencies can be found), use symlinking.
* Symlinking may fail on Windows for non-Admin users.
* - If not symlinking the entire directory, crawl the directory tree and
* hardlink all files (if possible), copying them if not.
*
* @param existingRoot is the original file or directory to link.
* @param destinationRoot is the new file or directory to create.
*/
export function link(existing: string, destination: string): void {
if (PRESERVE_SYMLINKS) {
mkdirSync(dirname(destination), { recursive: true });
symlinkSync(existing, destination);
return;
}
export function link(existingRoot: string, destinationRoot: string): void {
mkdirSync(dirname(destinationRoot), { recursive: true });

const stat = statSync(existing);
if (!stat.isDirectory()) {
if (PRESERVE_SYMLINKS) {
try {
linkSync(existing, destination);
} catch {
copyFileSync(existing, destination);
symlinkSync(existingRoot, destinationRoot);
return;
} catch (e: any) {
// On Windows, non-Admin users aren't allowed to create symlinks. In that case, fall back to the copying workflow.
const winNoSymlink = e.code === 'EPERM' && os.platform() === 'win32';

if (!winNoSymlink) {
throw e;
}
}
return;
}
// Fall back to the slow method
recurse(existingRoot, destinationRoot);

function recurse(existing: string, destination: string): void {
const stat = statSync(existing);
if (!stat.isDirectory()) {
try {
linkSync(existing, destination);
} catch {
copyFileSync(existing, destination);
}
return;
}

mkdirSync(destination, { recursive: true });
for (const file of readdirSync(existing)) {
link(join(existing, file), join(destination, file));
mkdirSync(destination, { recursive: true });
for (const file of readdirSync(existing)) {
recurse(join(existing, file), join(destination, file));
}
}
}

0 comments on commit fac6cbb

Please sign in to comment.