@@ -59,6 +59,36 @@ async function gracefulLstat(path: string): Promise<fs.Stats|null> {
5959 }
6060}
6161
62+ /**
63+ * Resolves a symlink to its linked path for a given path. Returns `null` if the path
64+ * does not exist on disk.
65+ */
66+ function gracefulReadlink ( path : string ) : string | null {
67+ try {
68+ return fs . readlinkSync ( path ) ;
69+ } catch ( e ) {
70+ if ( e . code === 'ENOENT' ) {
71+ return null ;
72+ }
73+ throw e ;
74+ }
75+ }
76+
77+ /**
78+ * Lists the names of files and directories that exist in the given path. Returns an empty
79+ * array if the path does not exist on disk.
80+ */
81+ async function gracefulReaddir ( path : string ) : Promise < string [ ] > {
82+ try {
83+ return await fs . promises . readdir ( path ) ;
84+ } catch ( e ) {
85+ if ( e . code === 'ENOENT' ) {
86+ return [ ] ;
87+ }
88+ throw e ;
89+ }
90+ }
91+
6292/**
6393 * Deletes the given module name from the current working directory (i.e. symlink root).
6494 * If the module name resolves to a directory, the directory is deleted. Otherwise the
@@ -81,11 +111,12 @@ async function unlink(moduleName: string) {
81111/** Asynchronously deletes a given directory (with contents). */
82112async function deleteDirectory ( p : string ) {
83113 log_verbose ( "Deleting children of" , p ) ;
84- for ( let entry of await fs . promises . readdir ( p ) ) {
114+ for ( let entry of await gracefulReaddir ( p ) ) {
85115 const childPath = path . join ( p , entry ) ;
86116 const stat = await gracefulLstat ( childPath ) ;
87117 if ( stat === null ) {
88- throw Error ( `File does not exist, but is listed as directory entry: ${ childPath } ` ) ;
118+ log_verbose ( `File does not exist, but is listed as directory entry: ${ childPath } ` ) ;
119+ continue ;
89120 }
90121 if ( stat . isDirectory ( ) ) {
91122 await deleteDirectory ( childPath ) ;
@@ -413,18 +444,25 @@ export async function main(args: string[], runfiles: Runfiles) {
413444 // then this is guaranteed to be not an artifact from a previous linker run. If not we need to
414445 // check.
415446 if ( runfiles . manifest && execroot && stats !== null && stats . isSymbolicLink ( ) ) {
416- const symlinkPath = fs . readlinkSync ( p ) . replace ( / \\ / g, '/' ) ;
417- if ( path . relative ( symlinkPath , target ) != '' &&
418- ! path . relative ( execroot , symlinkPath ) . startsWith ( '..' ) ) {
419- // Left-over out-of-date symlink from previous run. This can happen if switching between
420- // root configuration options such as `--noenable_runfiles` and/or
421- // `--spawn_strategy=standalone`. It can also happen if two different targets link the same
422- // module name to different targets in a non-sandboxed environment. The latter will lead to
423- // undeterministic behavior.
424- // TODO: can we detect the latter case and throw an apprioriate error?
425- log_verbose ( `Out-of-date symlink for ${ p } to ${ symlinkPath } detected. Target should be ${
426- target } . Unlinking.`) ;
427- await unlink ( p ) ;
447+ // Although `stats` suggests that the file exists as a symlink, it may have been deleted by
448+ // another process. Only proceed unlinking if the file actually still exists.
449+ const symlinkPathRaw = gracefulReadlink ( p ) ;
450+ if ( symlinkPathRaw !== null ) {
451+ const symlinkPath = symlinkPathRaw . replace ( / \\ / g, '/' ) ;
452+ if ( path . relative ( symlinkPath , target ) != '' &&
453+ ! path . relative ( execroot , symlinkPath ) . startsWith ( '..' ) ) {
454+ // Left-over out-of-date symlink from previous run. This can happen if switching between
455+ // root configuration options such as `--noenable_runfiles` and/or
456+ // `--spawn_strategy=standalone`. It can also happen if two different targets link the
457+ // same module name to different targets in a non-sandboxed environment. The latter will
458+ // lead to undeterministic behavior.
459+ // TODO: can we detect the latter case and throw an apprioriate error?
460+ log_verbose ( `Out-of-date symlink for ${ p } to ${ symlinkPath } detected. Target should be ${
461+ target } . Unlinking.`) ;
462+ await unlink ( p ) ;
463+ } else {
464+ log_verbose ( `The symlink at ${ p } no longer exists, so no need to unlink it.` ) ;
465+ }
428466 }
429467 }
430468 return symlink ( target , p ) ;
0 commit comments