diff --git a/src/caching/github-actions-cache.ts b/src/caching/github-actions-cache.ts index 5c144c772..41eaa1d24 100644 --- a/src/caching/github-actions-cache.ts +++ b/src/caching/github-actions-cache.ts @@ -618,10 +618,24 @@ ${blockIds.map((blockId) => ` ${blockId}`).join('\n' async #makeTarball(paths: string[], tempDir: string): Promise { // Create a manifest file so that we can pass a large number of files to // tar. - const manifestPath = pathlib.join(tempDir, 'manifest.txt'); - await fs.writeFile(manifestPath, paths.join('\n'), 'utf8'); + const manifestName = 'manifest.txt'; + const manifestPath = pathlib.join(tempDir, manifestName); + // The paths are absolute, and on Windows they use the native backslash + // separator. GNU tar (e.g. the one Git for Windows puts on PATH) treats a + // backslash as an escape character in its file lists, so convert to + // forward slashes. BSD tar accepts both. + await fs.writeFile( + manifestPath, + paths.map((path) => path.replaceAll('\\', '/')).join('\n'), + 'utf8', + ); const tarballPath = pathlib.join(tempDir, 'cache.tgz'); await new Promise((resolve, reject) => { + // Run tar from inside the temp directory and give it relative file names, + // rather than absolute ones. GNU tar on Windows interprets an absolute + // path with a drive letter (e.g. "D:\...") as a remote host, so it fails + // with "Cannot connect to D:" even though the file is local. See + // https://github.com/google/wireit/issues/1242. execFile( 'tar', [ @@ -636,7 +650,7 @@ ${blockIds.map((blockId) => ` ${blockId}`).join('\n' '--gzip', '--create', '--file', - tarballPath, + pathlib.basename(tarballPath), // Use absolute paths (note we use the short form because the long // form is --absolute-names on GNU tar, but --absolute-paths on BSD // tar). @@ -647,8 +661,9 @@ ${blockIds.map((blockId) => ` ${blockId}`).join('\n' // on disk. '--no-recursion', '--files-from', - manifestPath, + manifestName, ], + {cwd: tempDir}, (error: unknown) => { if (error != null) { reject(new Error(`tar error`, {cause: error})); @@ -786,9 +801,18 @@ class GitHubActionsCacheHit implements CacheHit { #extract(tarballPath: string): Promise { return new Promise((resolve, reject) => { + // Same rationale as in #makeTarball: give tar a relative file path so a + // drive-lettered absolute path is not mistaken for a remote host. execFile( 'tar', - ['--extract', '--file', tarballPath, '--gzip', '-P'], + [ + '--extract', + '--file', + pathlib.basename(tarballPath), + '--gzip', + '-P', + ], + {cwd: pathlib.dirname(tarballPath)}, (error: unknown) => { if (error != null) { reject(new Error(`tar error`, {cause: error}));