From fac6cbbeb5d9bcbbac8f9dff58001afb701a4065 Mon Sep 17 00:00:00 2001 From: Rico Hermans Date: Tue, 8 Aug 2023 11:47:22 +0200 Subject: [PATCH] fix(kernel): fast module loading fails on Windows (EPERM) (#4212) In https://github.com/aws/jsii/pull/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 --- packages/@jsii/kernel/src/link.ts | 58 +++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/packages/@jsii/kernel/src/link.ts b/packages/@jsii/kernel/src/link.ts index 8d518209da..830c228fbe 100644 --- a/packages/@jsii/kernel/src/link.ts +++ b/packages/@jsii/kernel/src/link.ts @@ -6,6 +6,7 @@ import { statSync, symlinkSync, } from 'fs'; +import * as os from 'os'; import { dirname, join } from 'path'; /** @@ -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)); + } } }