diff --git a/internal/linker/link_node_modules.ts b/internal/linker/link_node_modules.ts index 67c2b37e12..c6a5cbcce1 100644 --- a/internal/linker/link_node_modules.ts +++ b/internal/linker/link_node_modules.ts @@ -24,17 +24,21 @@ Include as much of the build output as you can without disclosing anything confi `); } -function symlink(target: string, path: string) { - if (fs.existsSync(path)) { +async function symlink(target: string, path: string) { + log_verbose(`symlink( ${path} -> ${target} )`); + // Use junction on Windows since symlinks require elevated permissions. + // We only link to directories so junctions work for us. + try{ + await fs.promises.symlink(target, path, 'junction'); + } catch(e){ + if(e.code !== 'ENOENT'){ + throw e; + } // We assume here that the path is already linked to the correct target. // Could add some logic that asserts it here, but we want to avoid an extra // filesystem access so we should only do it under some kind of strict mode. - return; } - log_verbose(`symlink( ${path} -> ${target} )`); - // Use junction on Windows since symlinks require elevated permissions. - // We only link to directories so junctions work for us. - fs.symlinkSync(target, path, 'junction'); + if (VERBOSE_LOGS) { // Be verbose about creating a bad symlink // Maybe this should fail in production as well, but again we want to avoid @@ -157,11 +161,25 @@ export class Runfiles { // TypeScript lib.es5.d.ts has a mistake: JSON.parse does accept Buffer. declare global { interface JSON { - parse(b: Buffer): any; + parse(b: {toString:()=>string}): any; + } +} + +// There is no fs.promises.exists function because +// node core is of the opinion that exists is always too racey to rely on. +async function exists(p:string){ + try{ + await fs.promises.stat(p) + return true; + } catch(e){ + if(e.code === 'ENOENT'){ + return false; + } + throw e; } } -export function main(args: string[], runfiles: Runfiles) { +export async function main(args: string[], runfiles: Runfiles) { if (!args || args.length < 1) throw new Error('link_node_modules.js requires one argument: modulesManifest path'); @@ -189,7 +207,7 @@ export function main(args: string[], runfiles: Runfiles) { } // Create the $pwd/node_modules directory that node will resolve from - symlink(rootDir, 'node_modules'); + await symlink(rootDir, 'node_modules'); process.chdir(rootDir); // Symlinks to packages need to reach back to the workspace/runfiles directory @@ -197,29 +215,41 @@ export function main(args: string[], runfiles: Runfiles) { const runfilesRelative = runfiles.dir ? path.relative('.', runfiles.dir) : undefined; // Now add symlinks to each of our first-party packages so they appear under the node_modules tree - for (const m of Object.keys(modules)) { + const links = [] + + const linkModule = async (name:string,modulePath:string)=>{ let target: string|undefined; // Look in the runfiles first // TODO: this could be a method in the Runfiles class if (runfiles.manifest) { - target = runfiles.lookupDirectory(modules[m]); + target = runfiles.lookupDirectory(modulePath); } else if (runfilesRelative) { - target = path.join(runfilesRelative, modules[m]); + target = path.join(runfilesRelative, modulePath); } // It sucks that we have to do a FS call here. // TODO: could we know which packages are statically linked?? - if (!target || !fs.existsSync(target)) { + if (!target || !await exists(target)) { // Try the execroot - target = path.join(workspaceRelative, toWorkspaceDir(modules[m])); + target = path.join(workspaceRelative, toWorkspaceDir(modulePath)); } - symlink(target, m); + + await symlink(target, name); } + for (const m of Object.keys(modules)) { + links.push(linkModule(m,modules[m])) + } + + await Promise.all(links); + return 0; } if (require.main === module) { - process.exitCode = main(process.argv.slice(2), new Runfiles()); + (async ()=>{ + process.exitCode = await main(process.argv.slice(2), new Runfiles()); + })(); } +